diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index a6dc31b53b..635fb85c89 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -124,6 +124,7 @@ add_library(common STATIC
     settings.h
     settings_input.cpp
     settings_input.h
+    socket_types.h
     spin_lock.cpp
     spin_lock.h
     stream.cpp
diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h
index 0ad9da2be9..cb004e0eb7 100644
--- a/src/common/announce_multiplayer_room.h
+++ b/src/common/announce_multiplayer_room.h
@@ -8,12 +8,11 @@
 #include <string>
 #include <vector>
 #include "common/common_types.h"
+#include "common/socket_types.h"
 #include "web_service/web_result.h"
 
 namespace AnnounceMultiplayerRoom {
 
-using MacAddress = std::array<u8, 6>;
-
 struct GameInfo {
     std::string name{""};
     u64 id{0};
@@ -24,7 +23,7 @@ struct Member {
     std::string nickname;
     std::string display_name;
     std::string avatar_url;
-    MacAddress mac_address;
+    Network::IPv4Address fake_ip;
     GameInfo game;
 };
 
@@ -75,10 +74,7 @@ public:
                                     const bool has_password, const GameInfo& preferred_game) = 0;
     /**
      * Adds a player information to the data that gets announced
-     * @param nickname The nickname of the player
-     * @param mac_address The MAC Address of the player
-     * @param game_id The title id of the game the player plays
-     * @param game_name The name of the game the player plays
+     * @param member The player to add
      */
     virtual void AddPlayer(const Member& member) = 0;
 
diff --git a/src/common/socket_types.h b/src/common/socket_types.h
new file mode 100644
index 0000000000..5bb309a442
--- /dev/null
+++ b/src/common/socket_types.h
@@ -0,0 +1,52 @@
+// Copyright 2022 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Network {
+
+/// Address families
+enum class Domain : u8 {
+    INET, ///< Address family for IPv4
+};
+
+/// Socket types
+enum class Type {
+    STREAM,
+    DGRAM,
+    RAW,
+    SEQPACKET,
+};
+
+/// Protocol values for sockets
+enum class Protocol : u8 {
+    ICMP,
+    TCP,
+    UDP,
+};
+
+/// Shutdown mode
+enum class ShutdownHow {
+    RD,
+    WR,
+    RDWR,
+};
+
+/// Array of IPv4 address
+using IPv4Address = std::array<u8, 4>;
+
+/// Cross-platform sockaddr structure
+struct SockAddrIn {
+    Domain family;
+    IPv4Address ip;
+    u16 portno;
+};
+
+constexpr u32 FLAG_MSG_PEEK = 0x2;
+constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
+constexpr u32 FLAG_O_NONBLOCK = 0x800;
+
+} // namespace Network
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4e39649a81..3230d71998 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -724,6 +724,8 @@ add_library(core STATIC
     internal_network/network_interface.cpp
     internal_network/network_interface.h
     internal_network/sockets.h
+    internal_network/socket_proxy.cpp
+    internal_network/socket_proxy.h
     loader/deconstructed_rom_directory.cpp
     loader/deconstructed_rom_directory.h
     loader/kip.cpp
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 2889973e46..42ed17187c 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -6,7 +6,6 @@
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/nifm/nifm.h"
-#include "core/hle/service/service.h"
 
 namespace {
 
@@ -271,142 +270,45 @@ public:
     }
 };
 
-class IGeneralService final : public ServiceFramework<IGeneralService> {
-public:
-    explicit IGeneralService(Core::System& system_);
+void IGeneralService::GetClientId(Kernel::HLERequestContext& ctx) {
+    static constexpr u32 client_id = 1;
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
 
-private:
-    void GetClientId(Kernel::HLERequestContext& ctx) {
-        static constexpr u32 client_id = 1;
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(ResultSuccess);
+    rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
+}
 
-        IPC::ResponseBuilder rb{ctx, 4};
-        rb.Push(ResultSuccess);
-        rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid
-    }
+void IGeneralService::CreateScanRequest(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NIFM, "called");
 
-    void CreateScanRequest(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_NIFM, "called");
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IScanRequest>(system);
+}
 
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IScanRequest>(system);
-    }
+void IGeneralService::CreateRequest(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NIFM, "called");
 
-    void CreateRequest(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_NIFM, "called");
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<IRequest>(system);
+}
 
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<IRequest>(system);
-    }
+void IGeneralService::GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
 
-    void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
+    const auto net_iface = Network::GetSelectedNetworkInterface();
 
-        const auto net_iface = Network::GetSelectedNetworkInterface();
-
-        const SfNetworkProfileData network_profile_data = [&net_iface] {
-            if (!net_iface) {
-                return SfNetworkProfileData{};
-            }
-
-            return SfNetworkProfileData{
-                .ip_setting_data{
-                    .ip_address_setting{
-                        .is_automatic{true},
-                        .current_address{Network::TranslateIPv4(net_iface->ip_address)},
-                        .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
-                        .gateway{Network::TranslateIPv4(net_iface->gateway)},
-                    },
-                    .dns_setting{
-                        .is_automatic{true},
-                        .primary_dns{1, 1, 1, 1},
-                        .secondary_dns{1, 0, 0, 1},
-                    },
-                    .proxy_setting{
-                        .enabled{false},
-                        .port{},
-                        .proxy_server{},
-                        .automatic_auth_enabled{},
-                        .user{},
-                        .password{},
-                    },
-                    .mtu{1500},
-                },
-                .uuid{0xdeadbeef, 0xdeadbeef},
-                .network_name{"yuzu Network"},
-                .wireless_setting_data{
-                    .ssid_length{12},
-                    .ssid{"yuzu Network"},
-                    .passphrase{"yuzupassword"},
-                },
-            };
-        }();
-
-        ctx.WriteBuffer(network_profile_data);
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-
-    void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
-        auto ipv4 = Network::GetHostIPv4Address();
-        if (!ipv4) {
-            LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
-            ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
+    SfNetworkProfileData network_profile_data = [&net_iface] {
+        if (!net_iface) {
+            return SfNetworkProfileData{};
         }
 
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushRaw(*ipv4);
-    }
-
-    void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_NIFM, "called");
-
-        ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c,
-                   "SfNetworkProfileData is not the correct size");
-        u128 uuid{};
-        auto buffer = ctx.ReadBuffer();
-        std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
-
-        IPC::ResponseBuilder rb{ctx, 6, 0, 1};
-
-        rb.Push(ResultSuccess);
-        rb.PushIpcInterface<INetworkProfile>(system);
-        rb.PushRaw<u128>(uuid);
-    }
-
-    void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
-        struct IpConfigInfo {
-            IpAddressSetting ip_address_setting{};
-            DnsSetting dns_setting{};
-        };
-        static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
-                      "IpConfigInfo has incorrect size.");
-
-        const auto net_iface = Network::GetSelectedNetworkInterface();
-
-        const IpConfigInfo ip_config_info = [&net_iface] {
-            if (!net_iface) {
-                return IpConfigInfo{};
-            }
-
-            return IpConfigInfo{
+        return SfNetworkProfileData{
+            .ip_setting_data{
                 .ip_address_setting{
                     .is_automatic{true},
                     .current_address{Network::TranslateIPv4(net_iface->ip_address)},
@@ -418,66 +320,177 @@ private:
                     .primary_dns{1, 1, 1, 1},
                     .secondary_dns{1, 0, 0, 1},
                 },
-            };
-        }();
+                .proxy_setting{
+                    .enabled{false},
+                    .port{},
+                    .proxy_server{},
+                    .automatic_auth_enabled{},
+                    .user{},
+                    .password{},
+                },
+                .mtu{1500},
+            },
+            .uuid{0xdeadbeef, 0xdeadbeef},
+            .network_name{"yuzu Network"},
+            .wireless_setting_data{
+                .ssid_length{12},
+                .ssid{"yuzu Network"},
+                .passphrase{"yuzupassword"},
+            },
+        };
+    }();
 
-        IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
-        rb.Push(ResultSuccess);
-        rb.PushRaw<IpConfigInfo>(ip_config_info);
+    // When we're connected to a room, spoof the hosts IP address
+    if (auto room_member = network.GetRoomMember().lock()) {
+        if (room_member->IsConnected()) {
+            network_profile_data.ip_setting_data.ip_address_setting.current_address =
+                room_member->GetFakeIpAddress();
+        }
     }
 
-    void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
+    ctx.WriteBuffer(network_profile_data);
 
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IGeneralService::RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IGeneralService::GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    auto ipv4 = Network::GetHostIPv4Address();
+    if (!ipv4) {
+        LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
+        ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
+    }
+
+    // When we're connected to a room, spoof the hosts IP address
+    if (auto room_member = network.GetRoomMember().lock()) {
+        if (room_member->IsConnected()) {
+            ipv4 = room_member->GetFakeIpAddress();
+        }
+    }
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushRaw(*ipv4);
+}
+void IGeneralService::CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NIFM, "called");
+
+    ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "SfNetworkProfileData is not the correct size");
+    u128 uuid{};
+    auto buffer = ctx.ReadBuffer();
+    std::memcpy(&uuid, buffer.data() + 8, sizeof(u128));
+
+    IPC::ResponseBuilder rb{ctx, 6, 0, 1};
+
+    rb.Push(ResultSuccess);
+    rb.PushIpcInterface<INetworkProfile>(system);
+    rb.PushRaw<u128>(uuid);
+}
+
+void IGeneralService::GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    struct IpConfigInfo {
+        IpAddressSetting ip_address_setting{};
+        DnsSetting dns_setting{};
+    };
+    static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
+                  "IpConfigInfo has incorrect size.");
+
+    const auto net_iface = Network::GetSelectedNetworkInterface();
+
+    IpConfigInfo ip_config_info = [&net_iface] {
+        if (!net_iface) {
+            return IpConfigInfo{};
+        }
+
+        return IpConfigInfo{
+            .ip_address_setting{
+                .is_automatic{true},
+                .current_address{Network::TranslateIPv4(net_iface->ip_address)},
+                .subnet_mask{Network::TranslateIPv4(net_iface->subnet_mask)},
+                .gateway{Network::TranslateIPv4(net_iface->gateway)},
+            },
+            .dns_setting{
+                .is_automatic{true},
+                .primary_dns{1, 1, 1, 1},
+                .secondary_dns{1, 0, 0, 1},
+            },
+        };
+    }();
+
+    // When we're connected to a room, spoof the hosts IP address
+    if (auto room_member = network.GetRoomMember().lock()) {
+        if (room_member->IsConnected()) {
+            ip_config_info.ip_address_setting.current_address = room_member->GetFakeIpAddress();
+        }
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
+    rb.Push(ResultSuccess);
+    rb.PushRaw<IpConfigInfo>(ip_config_info);
+}
+
+void IGeneralService::IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push<u8>(1);
+}
+
+void IGeneralService::GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    struct Output {
+        InternetConnectionType type{InternetConnectionType::WiFi};
+        u8 wifi_strength{3};
+        InternetConnectionStatus state{InternetConnectionStatus::Connected};
+    };
+    static_assert(sizeof(Output) == 0x3, "Output has incorrect size.");
+
+    constexpr Output out{};
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushRaw(out);
+}
+
+void IGeneralService::IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
+    LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    if (Network::GetHostIPv4Address().has_value()) {
+        rb.Push<u8>(1);
+    } else {
         rb.Push<u8>(0);
     }
+}
 
-    void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
+void IGeneralService::IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
+    LOG_ERROR(Service_NIFM, "(STUBBED) called");
 
-        struct Output {
-            InternetConnectionType type{InternetConnectionType::WiFi};
-            u8 wifi_strength{3};
-            InternetConnectionStatus state{InternetConnectionStatus::Connected};
-        };
-        static_assert(sizeof(Output) == 0x3, "Output has incorrect size.");
-
-        constexpr Output out{};
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushRaw(out);
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    if (Network::GetHostIPv4Address().has_value()) {
+        rb.Push<u8>(1);
+    } else {
+        rb.Push<u8>(0);
     }
-
-    void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        if (Network::GetHostIPv4Address().has_value()) {
-            rb.Push<u8>(1);
-        } else {
-            rb.Push<u8>(0);
-        }
-    }
-
-    void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NIFM, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        if (Network::GetHostIPv4Address().has_value()) {
-            rb.Push<u8>(1);
-        } else {
-            rb.Push<u8>(0);
-        }
-    }
-};
+}
 
 IGeneralService::IGeneralService(Core::System& system_)
-    : ServiceFramework{system_, "IGeneralService"} {
+    : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
     // clang-format off
     static const FunctionInfo functions[] = {
         {1, &IGeneralService::GetClientId, "GetClientId"},
@@ -528,6 +541,8 @@ IGeneralService::IGeneralService(Core::System& system_)
     RegisterHandlers(functions);
 }
 
+IGeneralService::~IGeneralService() = default;
+
 class NetworkInterface final : public ServiceFramework<NetworkInterface> {
 public:
     explicit NetworkInterface(const char* name, Core::System& system_)
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index 5f62d00142..48161be28a 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -3,6 +3,11 @@
 
 #pragma once
 
+#include "core/hle/service/service.h"
+#include "network/network.h"
+#include "network/room.h"
+#include "network/room_member.h"
+
 namespace Core {
 class System;
 }
@@ -16,4 +21,26 @@ namespace Service::NIFM {
 /// Registers all NIFM services with the specified service manager.
 void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
 
+class IGeneralService final : public ServiceFramework<IGeneralService> {
+public:
+    explicit IGeneralService(Core::System& system_);
+    ~IGeneralService() override;
+
+private:
+    void GetClientId(Kernel::HLERequestContext& ctx);
+    void CreateScanRequest(Kernel::HLERequestContext& ctx);
+    void CreateRequest(Kernel::HLERequestContext& ctx);
+    void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx);
+    void RemoveNetworkProfile(Kernel::HLERequestContext& ctx);
+    void GetCurrentIpAddress(Kernel::HLERequestContext& ctx);
+    void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx);
+    void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx);
+    void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx);
+    void GetInternetConnectionStatus(Kernel::HLERequestContext& ctx);
+    void IsEthernetCommunicationEnabled(Kernel::HLERequestContext& ctx);
+    void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx);
+
+    Network::RoomNetwork& network;
+};
+
 } // namespace Service::NIFM
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index c7194731ed..e08c3cb67a 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -9,12 +9,16 @@
 #include <fmt/format.h>
 
 #include "common/microprofile.h"
+#include "common/socket_types.h"
+#include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/k_thread.h"
 #include "core/hle/service/sockets/bsd.h"
 #include "core/hle/service/sockets/sockets_translate.h"
 #include "core/internal_network/network.h"
+#include "core/internal_network/socket_proxy.h"
 #include "core/internal_network/sockets.h"
+#include "network/network.h"
 
 namespace Service::Sockets {
 
@@ -472,7 +476,13 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
 
     LOG_INFO(Service, "New socket fd={}", fd);
 
-    descriptor.socket = std::make_unique<Network::Socket>();
+    auto room_member = room_network.GetRoomMember().lock();
+    if (room_member && room_member->IsConnected()) {
+        descriptor.socket = std::make_unique<Network::ProxySocket>(room_network);
+    } else {
+        descriptor.socket = std::make_unique<Network::Socket>();
+    }
+
     descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
     descriptor.is_connection_based = IsConnectionBased(type);
 
@@ -648,7 +658,7 @@ std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
         ASSERT(arg == 0);
         return {descriptor.flags, Errno::SUCCESS};
     case FcntlCmd::SETFL: {
-        const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
+        const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0;
         const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
         if (bsd_errno != Errno::SUCCESS) {
             return {-1, bsd_errno};
@@ -669,7 +679,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
         return Errno::BADF;
     }
 
-    Network::Socket* const socket = file_descriptors[fd]->socket.get();
+    Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
 
     if (optname == OptName::LINGER) {
         ASSERT(optlen == sizeof(Linger));
@@ -724,6 +734,8 @@ std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message)
     FileDescriptor& descriptor = *file_descriptors[fd];
 
     // Apply flags
+    using Network::FLAG_MSG_DONTWAIT;
+    using Network::FLAG_O_NONBLOCK;
     if ((flags & FLAG_MSG_DONTWAIT) != 0) {
         flags &= ~FLAG_MSG_DONTWAIT;
         if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
@@ -759,6 +771,8 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
     }
 
     // Apply flags
+    using Network::FLAG_MSG_DONTWAIT;
+    using Network::FLAG_O_NONBLOCK;
     if ((flags & FLAG_MSG_DONTWAIT) != 0) {
         flags &= ~FLAG_MSG_DONTWAIT;
         if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
@@ -857,8 +871,19 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co
     rb.PushEnum(bsd_errno);
 }
 
+void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) {
+    for (auto& optional_descriptor : file_descriptors) {
+        if (!optional_descriptor.has_value()) {
+            continue;
+        }
+        FileDescriptor& descriptor = *optional_descriptor;
+        descriptor.socket.get()->HandleProxyPacket(packet);
+    }
+}
+
 BSD::BSD(Core::System& system_, const char* name)
-    : ServiceFramework{system_, name, ServiceThreadType::CreateNew} {
+    : ServiceFramework{system_, name, ServiceThreadType::CreateNew}, room_network{
+                                                                         system_.GetRoomNetwork()} {
     // clang-format off
     static const FunctionInfo functions[] = {
         {0, &BSD::RegisterClient, "RegisterClient"},
@@ -899,6 +924,13 @@ BSD::BSD(Core::System& system_, const char* name)
     // clang-format on
 
     RegisterHandlers(functions);
+
+    if (auto room_member = room_network.GetRoomMember().lock()) {
+        proxy_packet_received = room_member->BindOnProxyPacketReceived(
+            [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
+    } else {
+        LOG_ERROR(Service, "Network isn't initalized");
+    }
 }
 
 BSD::~BSD() = default;
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 9ea36428d6..81e855e0fa 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -7,14 +7,17 @@
 #include <vector>
 
 #include "common/common_types.h"
+#include "common/socket_types.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/sockets/sockets.h"
+#include "network/network.h"
 
 namespace Core {
 class System;
 }
 
 namespace Network {
+class SocketBase;
 class Socket;
 } // namespace Network
 
@@ -30,7 +33,7 @@ private:
     static constexpr size_t MAX_FD = 128;
 
     struct FileDescriptor {
-        std::unique_ptr<Network::Socket> socket;
+        std::unique_ptr<Network::SocketBase> socket;
         s32 flags = 0;
         bool is_connection_based = false;
     };
@@ -165,6 +168,14 @@ private:
     void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
 
     std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
+
+    Network::RoomNetwork& room_network;
+
+    /// Callback to parse and handle a received wifi packet.
+    void OnProxyPacketReceived(const Network::ProxyPacket& packet);
+
+    // Callback identifier for the OnProxyPacketReceived event.
+    Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
 };
 
 class BSDCFG final : public ServiceFramework<BSDCFG> {
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index b735b00fcc..31b7dad33a 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -22,7 +22,9 @@ enum class Errno : u32 {
     AGAIN = 11,
     INVAL = 22,
     MFILE = 24,
+    MSGSIZE = 90,
     NOTCONN = 107,
+    TIMEDOUT = 110,
 };
 
 enum class Domain : u32 {
@@ -96,10 +98,6 @@ struct Linger {
     u32 linger;
 };
 
-constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
-
-constexpr u32 FLAG_O_NONBLOCK = 0x800;
-
 /// Registers all Sockets services with the specified service manager.
 void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
 
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index 2db10ec810..023aa04865 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -25,6 +25,8 @@ Errno Translate(Network::Errno value) {
         return Errno::MFILE;
     case Network::Errno::NOTCONN:
         return Errno::NOTCONN;
+    case Network::Errno::TIMEDOUT:
+        return Errno::TIMEDOUT;
     default:
         UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
         return Errno::SUCCESS;
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 36c43cc8ff..160cc83e4f 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -32,6 +32,7 @@
 #include "core/internal_network/network.h"
 #include "core/internal_network/network_interface.h"
 #include "core/internal_network/sockets.h"
+#include "network/network.h"
 
 namespace Network {
 
@@ -114,7 +115,10 @@ Errno TranslateNativeError(int e) {
         return Errno::NETDOWN;
     case WSAENETUNREACH:
         return Errno::NETUNREACH;
+    case WSAEMSGSIZE:
+        return Errno::MSGSIZE;
     default:
+        UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
         return Errno::OTHER;
     }
 }
@@ -125,7 +129,6 @@ using SOCKET = int;
 using WSAPOLLFD = pollfd;
 using ULONG = u64;
 
-constexpr SOCKET INVALID_SOCKET = -1;
 constexpr SOCKET SOCKET_ERROR = -1;
 
 constexpr int SD_RECEIVE = SHUT_RD;
@@ -206,7 +209,10 @@ Errno TranslateNativeError(int e) {
         return Errno::NETDOWN;
     case ENETUNREACH:
         return Errno::NETUNREACH;
+    case EMSGSIZE:
+        return Errno::MSGSIZE;
     default:
+        UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
         return Errno::OTHER;
     }
 }
@@ -329,16 +335,6 @@ PollEvents TranslatePollRevents(short revents) {
     return result;
 }
 
-template <typename T>
-Errno SetSockOpt(SOCKET fd, int option, T value) {
-    const int result =
-        setsockopt(fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
-    if (result != SOCKET_ERROR) {
-        return Errno::SUCCESS;
-    }
-    return GetAndLogLastError();
-}
-
 } // Anonymous namespace
 
 NetworkInstance::NetworkInstance() {
@@ -350,26 +346,15 @@ NetworkInstance::~NetworkInstance() {
 }
 
 std::optional<IPv4Address> GetHostIPv4Address() {
-    const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
-    const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
-    if (network_interfaces.size() == 0) {
-        LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
+    const auto interface = Network::GetSelectedNetworkInterface();
+    if (!interface.has_value()) {
+        LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
         return {};
     }
 
-    const auto res =
-        std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
-            return iface.name == selected_network_interface;
-        });
-
-    if (res != network_interfaces.end()) {
-        char ip_addr[16] = {};
-        ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
-        return TranslateIPv4(res->ip_address);
-    } else {
-        LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
-        return {};
-    }
+    char ip_addr[16] = {};
+    ASSERT(inet_ntop(AF_INET, &interface->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
+    return TranslateIPv4(interface->ip_address);
 }
 
 std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
@@ -412,7 +397,19 @@ Socket::~Socket() {
     fd = INVALID_SOCKET;
 }
 
-Socket::Socket(Socket&& rhs) noexcept : fd{std::exchange(rhs.fd, INVALID_SOCKET)} {}
+Socket::Socket(Socket&& rhs) noexcept {
+    fd = std::exchange(rhs.fd, INVALID_SOCKET);
+}
+
+template <typename T>
+Errno Socket::SetSockOpt(SOCKET _fd, int option, T value) {
+    const int result =
+        setsockopt(_fd, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
+    if (result != SOCKET_ERROR) {
+        return Errno::SUCCESS;
+    }
+    return GetAndLogLastError();
+}
 
 Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
     fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
@@ -423,7 +420,7 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
     return GetAndLogLastError();
 }
 
-std::pair<Socket::AcceptResult, Errno> Socket::Accept() {
+std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
     sockaddr addr;
     socklen_t addrlen = sizeof(addr);
     const SOCKET new_socket = accept(fd, &addr, &addrlen);
@@ -634,4 +631,8 @@ bool Socket::IsOpened() const {
     return fd != INVALID_SOCKET;
 }
 
+void Socket::HandleProxyPacket(const ProxyPacket& packet) {
+    LOG_WARNING(Network, "ProxyPacket received, but not in Proxy mode!");
+}
+
 } // namespace Network
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index 10e5ef10d6..36994c22e1 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -8,6 +8,7 @@
 
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "common/socket_types.h"
 
 #ifdef _WIN32
 #include <winsock2.h>
@@ -17,6 +18,7 @@
 
 namespace Network {
 
+class SocketBase;
 class Socket;
 
 /// Error code for network functions
@@ -31,46 +33,11 @@ enum class Errno {
     HOSTUNREACH,
     NETDOWN,
     NETUNREACH,
+    TIMEDOUT,
+    MSGSIZE,
     OTHER,
 };
 
-/// Address families
-enum class Domain {
-    INET, ///< Address family for IPv4
-};
-
-/// Socket types
-enum class Type {
-    STREAM,
-    DGRAM,
-    RAW,
-    SEQPACKET,
-};
-
-/// Protocol values for sockets
-enum class Protocol {
-    ICMP,
-    TCP,
-    UDP,
-};
-
-/// Shutdown mode
-enum class ShutdownHow {
-    RD,
-    WR,
-    RDWR,
-};
-
-/// Array of IPv4 address
-using IPv4Address = std::array<u8, 4>;
-
-/// Cross-platform sockaddr structure
-struct SockAddrIn {
-    Domain family;
-    IPv4Address ip;
-    u16 portno;
-};
-
 /// Cross-platform poll fd structure
 
 enum class PollEvents : u16 {
@@ -86,7 +53,7 @@ enum class PollEvents : u16 {
 DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
 
 struct PollFD {
-    Socket* socket;
+    SocketBase* socket;
     PollEvents events;
     PollEvents revents;
 };
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
new file mode 100644
index 0000000000..6e89248221
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -0,0 +1,282 @@
+// Copyright 2022 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <thread>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+#include "core/internal_network/socket_proxy.h"
+
+namespace Network {
+
+ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
+
+ProxySocket::ProxySocket(ProxySocket&& rhs) noexcept : room_network{rhs.room_network} {
+    fd = std::exchange(rhs.fd, INVALID_SOCKET);
+}
+
+ProxySocket::~ProxySocket() {
+    if (fd == INVALID_SOCKET) {
+        return;
+    }
+    fd = INVALID_SOCKET;
+}
+
+void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
+    if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno ||
+        closed) {
+        return;
+    }
+    std::lock_guard<std::mutex> guard(packets_mutex);
+    received_packets.push(packet);
+}
+
+template <typename T>
+Errno ProxySocket::SetSockOpt(SOCKET _fd, int option, T value) {
+    socket_options[option] = reinterpret_cast<const char*>(&value);
+    return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) {
+    protocol = socket_protocol;
+    socket_options[0x1008] = reinterpret_cast<const char*>(&type);
+
+    return Errno::SUCCESS;
+}
+
+std::pair<ProxySocket::AcceptResult, Errno> ProxySocket::Accept() {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return {AcceptResult{}, Errno::SUCCESS};
+}
+
+Errno ProxySocket::Connect(SockAddrIn addr_in) {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return Errno::SUCCESS;
+}
+
+std::pair<SockAddrIn, Errno> ProxySocket::GetPeerName() {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return {SockAddrIn{}, Errno::SUCCESS};
+}
+
+std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return {SockAddrIn{}, Errno::SUCCESS};
+}
+
+Errno ProxySocket::Bind(SockAddrIn addr) {
+    if (is_bound) {
+        LOG_WARNING(Network, "Rebinding Socket is unimplemented!");
+        return Errno::SUCCESS;
+    }
+    local_endpoint = addr;
+    is_bound = true;
+
+    return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Listen(s32 backlog) {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return Errno::SUCCESS;
+}
+
+Errno ProxySocket::Shutdown(ShutdownHow how) {
+    LOG_WARNING(Network, "(STUBBED) called");
+    return Errno::SUCCESS;
+}
+
+std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) {
+    LOG_WARNING(Network, "(STUBBED) called");
+    ASSERT(flags == 0);
+    ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+    return {static_cast<s32>(0), Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
+    ASSERT(flags == 0);
+    ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+
+    {
+        std::lock_guard<std::mutex> guard(packets_mutex);
+        if (received_packets.size() > 0) {
+            return ReceivePacket(flags, message, addr, message.size());
+        }
+    }
+
+    if (blocking) {
+        if (receive_timeout > 0) {
+            std::this_thread::sleep_for(std::chrono::milliseconds(receive_timeout));
+        }
+    } else {
+        return {-1, Errno::AGAIN};
+    }
+
+    std::lock_guard<std::mutex> guard(packets_mutex);
+    if (received_packets.size() > 0) {
+        return ReceivePacket(flags, message, addr, message.size());
+    }
+
+    return {-1, Errno::TIMEDOUT};
+}
+
+std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message,
+                                                 SockAddrIn* addr, std::size_t max_length) {
+    ProxyPacket& packet = received_packets.front();
+    if (addr) {
+        addr->family = Domain::INET;
+        addr->ip = packet.local_endpoint.ip;         // The senders ip address
+        addr->portno = packet.local_endpoint.portno; // The senders port number
+    }
+
+    bool peek = (flags & FLAG_MSG_PEEK) != 0;
+    std::size_t read_bytes;
+    if (packet.data.size() > max_length) {
+        read_bytes = max_length;
+        message.clear();
+        std::copy(packet.data.begin(), packet.data.begin() + read_bytes,
+                  std::back_inserter(message));
+        message.resize(max_length);
+
+        if (protocol == Protocol::UDP) {
+            if (!peek) {
+                received_packets.pop();
+            }
+            return {-1, Errno::MSGSIZE};
+        } else if (protocol == Protocol::TCP) {
+            std::vector<u8> numArray(packet.data.size() - max_length);
+            std::copy(packet.data.begin() + max_length, packet.data.end(),
+                      std::back_inserter(numArray));
+            packet.data = numArray;
+        }
+    } else {
+        read_bytes = packet.data.size();
+        message.clear();
+        std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message));
+        message.resize(max_length);
+        if (!peek) {
+            received_packets.pop();
+        }
+    }
+
+    return {static_cast<u32>(read_bytes), Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flags) {
+    LOG_WARNING(Network, "(STUBBED) called");
+    ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
+    ASSERT(flags == 0);
+
+    return {static_cast<s32>(0), Errno::SUCCESS};
+}
+
+void ProxySocket::SendPacket(ProxyPacket& packet) {
+    if (auto room_member = room_network.GetRoomMember().lock()) {
+        if (room_member->IsConnected()) {
+            room_member->SendProxyPacket(packet);
+        }
+    }
+}
+
+std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, const std::vector<u8>& message,
+                                          const SockAddrIn* addr) {
+    ASSERT(flags == 0);
+
+    if (!is_bound) {
+        LOG_ERROR(Network, "ProxySocket is not bound!");
+        return {static_cast<s32>(message.size()), Errno::SUCCESS};
+    }
+
+    if (auto room_member = room_network.GetRoomMember().lock()) {
+        if (!room_member->IsConnected()) {
+            return {static_cast<s32>(message.size()), Errno::SUCCESS};
+        }
+    }
+
+    ProxyPacket packet;
+    packet.local_endpoint = local_endpoint;
+    packet.remote_endpoint = *addr;
+    packet.protocol = protocol;
+    packet.broadcast = broadcast;
+
+    auto& ip = local_endpoint.ip;
+    auto ipv4 = Network::GetHostIPv4Address();
+    // If the ip is all zeroes (INADDR_ANY) or if it matches the hosts ip address,
+    // replace it with a "fake" routing address
+    if (std::all_of(ip.begin(), ip.end(), [](u8 i) { return i == 0; }) || (ipv4 && ipv4 == ip)) {
+        if (auto room_member = room_network.GetRoomMember().lock()) {
+            packet.local_endpoint.ip = room_member->GetFakeIpAddress();
+        }
+    }
+
+    packet.data.clear();
+    std::copy(message.begin(), message.end(), std::back_inserter(packet.data));
+
+    SendPacket(packet);
+
+    return {static_cast<s32>(message.size()), Errno::SUCCESS};
+}
+
+Errno ProxySocket::Close() {
+    fd = INVALID_SOCKET;
+    closed = true;
+
+    return Errno::SUCCESS;
+}
+
+Errno ProxySocket::SetLinger(bool enable, u32 linger) {
+    struct Linger {
+        u16 linger_enable;
+        u16 linger_time;
+    } values;
+    values.linger_enable = enable ? 1 : 0;
+    values.linger_time = static_cast<u16>(linger);
+
+    return SetSockOpt(fd, SO_LINGER, values);
+}
+
+Errno ProxySocket::SetReuseAddr(bool enable) {
+    return SetSockOpt<u32>(fd, SO_REUSEADDR, enable ? 1 : 0);
+}
+
+Errno ProxySocket::SetBroadcast(bool enable) {
+    broadcast = enable;
+    return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0);
+}
+
+Errno ProxySocket::SetSndBuf(u32 value) {
+    return SetSockOpt(fd, SO_SNDBUF, value);
+}
+
+Errno ProxySocket::SetKeepAlive(bool enable) {
+    return Errno::SUCCESS;
+}
+
+Errno ProxySocket::SetRcvBuf(u32 value) {
+    return SetSockOpt(fd, SO_RCVBUF, value);
+}
+
+Errno ProxySocket::SetSndTimeo(u32 value) {
+    send_timeout = value;
+    return SetSockOpt(fd, SO_SNDTIMEO, static_cast<int>(value));
+}
+
+Errno ProxySocket::SetRcvTimeo(u32 value) {
+    receive_timeout = value;
+    return SetSockOpt(fd, SO_RCVTIMEO, static_cast<int>(value));
+}
+
+Errno ProxySocket::SetNonBlock(bool enable) {
+    blocking = !enable;
+    return Errno::SUCCESS;
+}
+
+bool ProxySocket::IsOpened() const {
+    return fd != INVALID_SOCKET;
+}
+
+} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h
new file mode 100644
index 0000000000..ef7d5b5543
--- /dev/null
+++ b/src/core/internal_network/socket_proxy.h
@@ -0,0 +1,102 @@
+// Copyright 2022 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include <queue>
+
+#include "core/internal_network/sockets.h"
+#include "network/network.h"
+
+namespace Network {
+
+class ProxySocket : public SocketBase {
+public:
+    ProxySocket(RoomNetwork& room_network_) noexcept;
+    ~ProxySocket() override;
+
+    ProxySocket(const ProxySocket&) = delete;
+    ProxySocket& operator=(const ProxySocket&) = delete;
+
+    ProxySocket(ProxySocket&& rhs) noexcept;
+
+    // Avoid closing sockets implicitly
+    ProxySocket& operator=(ProxySocket&&) noexcept = delete;
+
+    void HandleProxyPacket(const ProxyPacket& packet);
+
+    Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override;
+
+    Errno Close() override;
+
+    std::pair<AcceptResult, Errno> Accept() override;
+
+    Errno Connect(SockAddrIn addr_in) override;
+
+    std::pair<SockAddrIn, Errno> GetPeerName() override;
+
+    std::pair<SockAddrIn, Errno> GetSockName() override;
+
+    Errno Bind(SockAddrIn addr) override;
+
+    Errno Listen(s32 backlog) override;
+
+    Errno Shutdown(ShutdownHow how) override;
+
+    std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
+
+    std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
+
+    std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr,
+                                        std::size_t max_length);
+
+    std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
+
+    void SendPacket(ProxyPacket& packet);
+
+    std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+                                 const SockAddrIn* addr) override;
+
+    Errno SetLinger(bool enable, u32 linger) override;
+
+    Errno SetReuseAddr(bool enable) override;
+
+    Errno SetBroadcast(bool enable) override;
+
+    Errno SetKeepAlive(bool enable) override;
+
+    Errno SetSndBuf(u32 value) override;
+
+    Errno SetRcvBuf(u32 value) override;
+
+    Errno SetSndTimeo(u32 value) override;
+
+    Errno SetRcvTimeo(u32 value) override;
+
+    Errno SetNonBlock(bool enable) override;
+
+    template <typename T>
+    Errno SetSockOpt(SOCKET fd, int option, T value);
+
+    bool IsOpened() const override;
+
+    bool broadcast = false;
+    bool closed = false;
+    u32 send_timeout = 0;
+    u32 receive_timeout = 0;
+    std::map<int, const char*> socket_options;
+    bool is_bound = false;
+    SockAddrIn local_endpoint{};
+    bool blocking = true;
+    std::queue<ProxyPacket> received_packets;
+    Protocol protocol;
+
+    std::mutex packets_mutex;
+
+    RoomNetwork& room_network;
+};
+
+} // namespace Network
diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h
index 77e27e9280..92dc499936 100644
--- a/src/core/internal_network/sockets.h
+++ b/src/core/internal_network/sockets.h
@@ -14,20 +14,92 @@
 
 #include "common/common_types.h"
 #include "core/internal_network/network.h"
+#include "network/network.h"
 
 // TODO: C++20 Replace std::vector usages with std::span
 
 namespace Network {
 
-class Socket {
+class SocketBase {
 public:
+#ifdef YUZU_UNIX
+    using SOCKET = int;
+    static constexpr SOCKET INVALID_SOCKET = -1;
+    static constexpr SOCKET SOCKET_ERROR = -1;
+#endif
+
     struct AcceptResult {
-        std::unique_ptr<Socket> socket;
+        std::unique_ptr<SocketBase> socket;
         SockAddrIn sockaddr_in;
     };
+    virtual ~SocketBase() {}
 
-    explicit Socket() = default;
-    ~Socket();
+    virtual SocketBase& operator=(const SocketBase&) = delete;
+
+    // Avoid closing sockets implicitly
+    virtual SocketBase& operator=(SocketBase&&) noexcept = delete;
+
+    virtual Errno Initialize(Domain domain, Type type, Protocol protocol) = 0;
+
+    virtual Errno Close() = 0;
+
+    virtual std::pair<AcceptResult, Errno> Accept() = 0;
+
+    virtual Errno Connect(SockAddrIn addr_in) = 0;
+
+    virtual std::pair<SockAddrIn, Errno> GetPeerName() = 0;
+
+    virtual std::pair<SockAddrIn, Errno> GetSockName() = 0;
+
+    virtual Errno Bind(SockAddrIn addr) = 0;
+
+    virtual Errno Listen(s32 backlog) = 0;
+
+    virtual Errno Shutdown(ShutdownHow how) = 0;
+
+    virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0;
+
+    virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message,
+                                           SockAddrIn* addr) = 0;
+
+    virtual std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) = 0;
+
+    virtual std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+                                         const SockAddrIn* addr) = 0;
+
+    virtual Errno SetLinger(bool enable, u32 linger) = 0;
+
+    virtual Errno SetReuseAddr(bool enable) = 0;
+
+    virtual Errno SetKeepAlive(bool enable) = 0;
+
+    virtual Errno SetBroadcast(bool enable) = 0;
+
+    virtual Errno SetSndBuf(u32 value) = 0;
+
+    virtual Errno SetRcvBuf(u32 value) = 0;
+
+    virtual Errno SetSndTimeo(u32 value) = 0;
+
+    virtual Errno SetRcvTimeo(u32 value) = 0;
+
+    virtual Errno SetNonBlock(bool enable) = 0;
+
+    virtual bool IsOpened() const = 0;
+
+    virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
+
+#if defined(_WIN32)
+    SOCKET fd = INVALID_SOCKET;
+#elif YUZU_UNIX
+    int fd = -1;
+#endif
+};
+
+class Socket : public SocketBase {
+public:
+    Socket() = default;
+    ~Socket() override;
 
     Socket(const Socket&) = delete;
     Socket& operator=(const Socket&) = delete;
@@ -37,57 +109,57 @@ public:
     // Avoid closing sockets implicitly
     Socket& operator=(Socket&&) noexcept = delete;
 
-    Errno Initialize(Domain domain, Type type, Protocol protocol);
+    Errno Initialize(Domain domain, Type type, Protocol protocol) override;
 
-    Errno Close();
+    Errno Close() override;
 
-    std::pair<AcceptResult, Errno> Accept();
+    std::pair<AcceptResult, Errno> Accept() override;
 
-    Errno Connect(SockAddrIn addr_in);
+    Errno Connect(SockAddrIn addr_in) override;
 
-    std::pair<SockAddrIn, Errno> GetPeerName();
+    std::pair<SockAddrIn, Errno> GetPeerName() override;
 
-    std::pair<SockAddrIn, Errno> GetSockName();
+    std::pair<SockAddrIn, Errno> GetSockName() override;
 
-    Errno Bind(SockAddrIn addr);
+    Errno Bind(SockAddrIn addr) override;
 
-    Errno Listen(s32 backlog);
+    Errno Listen(s32 backlog) override;
 
-    Errno Shutdown(ShutdownHow how);
+    Errno Shutdown(ShutdownHow how) override;
 
-    std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message);
+    std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
 
-    std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr);
+    std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
 
-    std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags);
+    std::pair<s32, Errno> Send(const std::vector<u8>& message, int flags) override;
 
-    std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message, const SockAddrIn* addr);
+    std::pair<s32, Errno> SendTo(u32 flags, const std::vector<u8>& message,
+                                 const SockAddrIn* addr) override;
 
-    Errno SetLinger(bool enable, u32 linger);
+    Errno SetLinger(bool enable, u32 linger) override;
 
-    Errno SetReuseAddr(bool enable);
+    Errno SetReuseAddr(bool enable) override;
 
-    Errno SetKeepAlive(bool enable);
+    Errno SetKeepAlive(bool enable) override;
 
-    Errno SetBroadcast(bool enable);
+    Errno SetBroadcast(bool enable) override;
 
-    Errno SetSndBuf(u32 value);
+    Errno SetSndBuf(u32 value) override;
 
-    Errno SetRcvBuf(u32 value);
+    Errno SetRcvBuf(u32 value) override;
 
-    Errno SetSndTimeo(u32 value);
+    Errno SetSndTimeo(u32 value) override;
 
-    Errno SetRcvTimeo(u32 value);
+    Errno SetRcvTimeo(u32 value) override;
 
-    Errno SetNonBlock(bool enable);
+    Errno SetNonBlock(bool enable) override;
 
-    bool IsOpened() const;
+    template <typename T>
+    Errno SetSockOpt(SOCKET fd, int option, T value);
 
-#if defined(_WIN32)
-    SOCKET fd = INVALID_SOCKET;
-#elif YUZU_UNIX
-    int fd = -1;
-#endif
+    bool IsOpened() const override;
+
+    void HandleProxyPacket(const ProxyPacket& packet) override;
 };
 
 std::pair<s32, Errno> Poll(std::vector<PollFD>& poll_fds, s32 timeout);
diff --git a/src/network/room.cpp b/src/network/room.cpp
index 3fc3a0383c..b06797bf11 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -20,9 +20,7 @@ namespace Network {
 
 class Room::RoomImpl {
 public:
-    // This MAC address is used to generate a 'Nintendo' like Mac address.
-    const MacAddress NintendoOUI;
-    std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
+    std::mt19937 random_gen; ///< Random number generator. Used for GenerateFakeIPAddress
 
     ENetHost* server = nullptr; ///< Network interface.
 
@@ -35,10 +33,9 @@ public:
     std::string password; ///< The password required to connect to this room.
 
     struct Member {
-        std::string nickname;        ///< The nickname of the member.
-        std::string console_id_hash; ///< A hash of the console ID of the member.
-        GameInfo game_info;          ///< The current game of the member
-        MacAddress mac_address;      ///< The assigned mac address of the member.
+        std::string nickname; ///< The nickname of the member.
+        GameInfo game_info;   ///< The current game of the member
+        IPv4Address fake_ip;  ///< The assigned fake ip address of the member.
         /// Data of the user, often including authenticated forum username.
         VerifyUser::UserData user_data;
         ENetPeer* peer; ///< The remote peer.
@@ -51,8 +48,7 @@ public:
     IPBanList ip_ban_list;             ///< List of banned IP addresses
     mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
 
-    RoomImpl()
-        : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {}
+    RoomImpl() : random_gen(std::random_device()()) {}
 
     /// Thread that receives and dispatches network packets
     std::unique_ptr<std::thread> room_thread;
@@ -101,16 +97,10 @@ public:
     bool IsValidNickname(const std::string& nickname) const;
 
     /**
-     * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the
+     * Returns whether the fake ip address is valid, ie. isn't already taken by someone else in the
      * room.
      */
-    bool IsValidMacAddress(const MacAddress& address) const;
-
-    /**
-     * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in
-     * the room.
-     */
-    bool IsValidConsoleId(const std::string& console_id_hash) const;
+    bool IsValidFakeIPAddress(const IPv4Address& address) const;
 
     /**
      * Returns whether a user has mod permissions.
@@ -128,15 +118,9 @@ public:
     void SendNameCollision(ENetPeer* client);
 
     /**
-     * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid.
+     * Sends a ID_ROOM_IP_COLLISION message telling the client that the IP is invalid.
      */
-    void SendMacCollision(ENetPeer* client);
-
-    /**
-     * Sends a IdConsoleIdCollison message telling the client that another member with the same
-     * console ID exists.
-     */
-    void SendConsoleIdCollision(ENetPeer* client);
+    void SendIPCollision(ENetPeer* client);
 
     /**
      * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
@@ -152,13 +136,13 @@ public:
      * Notifies the member that its connection attempt was successful,
      * and it is now part of the room.
      */
-    void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
+    void SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip);
 
     /**
      * Notifies the member that its connection attempt was successful,
      * and it is now part of the room, and it has been granted mod permissions.
      */
-    void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address);
+    void SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip);
 
     /**
      * Sends a IdHostKicked message telling the client that they have been kicked.
@@ -210,7 +194,7 @@ public:
      * <u32> num_members: the number of currently joined clients
      * This is followed by the following three values for each member:
      * <String> nickname of that member
-     * <MacAddress> mac_address of that member
+     * <IPv4Address> fake_ip of that member
      * <String> game_name of that member
      */
     void BroadcastRoomInformation();
@@ -219,13 +203,13 @@ public:
      * Generates a free MAC address to assign to a new client.
      * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
      */
-    MacAddress GenerateMacAddress();
+    IPv4Address GenerateFakeIPAddress();
 
     /**
      * Broadcasts this packet to all members except the sender.
      * @param event The ENet event containing the data
      */
-    void HandleWifiPacket(const ENetEvent* event);
+    void HandleProxyPacket(const ENetEvent* event);
 
     /**
      * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
@@ -250,7 +234,7 @@ public:
 void Room::RoomImpl::ServerLoop() {
     while (state != State::Closed) {
         ENetEvent event;
-        if (enet_host_service(server, &event, 16) > 0) {
+        if (enet_host_service(server, &event, 50) > 0) {
             switch (event.type) {
             case ENET_EVENT_TYPE_RECEIVE:
                 switch (event.packet->data[0]) {
@@ -260,8 +244,8 @@ void Room::RoomImpl::ServerLoop() {
                 case IdSetGameInfo:
                     HandleGameNamePacket(&event);
                     break;
-                case IdWifiPacket:
-                    HandleWifiPacket(&event);
+                case IdProxyPacket:
+                    HandleProxyPacket(&event);
                     break;
                 case IdChatMessage:
                     HandleChatPacket(&event);
@@ -313,11 +297,8 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
     std::string nickname;
     packet.Read(nickname);
 
-    std::string console_id_hash;
-    packet.Read(console_id_hash);
-
-    MacAddress preferred_mac;
-    packet.Read(preferred_mac);
+    IPv4Address preferred_fake_ip;
+    packet.Read(preferred_fake_ip);
 
     u32 client_version;
     packet.Read(client_version);
@@ -338,20 +319,15 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
         return;
     }
 
-    if (preferred_mac != NoPreferredMac) {
-        // Verify if the preferred mac is available
-        if (!IsValidMacAddress(preferred_mac)) {
-            SendMacCollision(event->peer);
+    if (preferred_fake_ip != NoPreferredIP) {
+        // Verify if the preferred fake ip is available
+        if (!IsValidFakeIPAddress(preferred_fake_ip)) {
+            SendIPCollision(event->peer);
             return;
         }
     } else {
-        // Assign a MAC address of this client automatically
-        preferred_mac = GenerateMacAddress();
-    }
-
-    if (!IsValidConsoleId(console_id_hash)) {
-        SendConsoleIdCollision(event->peer);
-        return;
+        // Assign a fake ip address of this client automatically
+        preferred_fake_ip = GenerateFakeIPAddress();
     }
 
     if (client_version != network_version) {
@@ -361,8 +337,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
 
     // At this point the client is ready to be added to the room.
     Member member{};
-    member.mac_address = preferred_mac;
-    member.console_id_hash = console_id_hash;
+    member.fake_ip = preferred_fake_ip;
     member.nickname = nickname;
     member.peer = event->peer;
 
@@ -408,9 +383,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
     // Notify everyone that the room information has changed.
     BroadcastRoomInformation();
     if (HasModPermission(event->peer)) {
-        SendJoinSuccessAsMod(event->peer, preferred_mac);
+        SendJoinSuccessAsMod(event->peer, preferred_fake_ip);
     } else {
-        SendJoinSuccess(event->peer, preferred_mac);
+        SendJoinSuccess(event->peer, preferred_fake_ip);
     }
 }
 
@@ -575,19 +550,11 @@ bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
                        [&nickname](const auto& member) { return member.nickname != nickname; });
 }
 
-bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
-    // A MAC address is valid if it is not already taken by anybody else in the room.
+bool Room::RoomImpl::IsValidFakeIPAddress(const IPv4Address& address) const {
+    // An IP address is valid if it is not already taken by anybody else in the room.
     std::lock_guard lock(member_mutex);
     return std::all_of(members.begin(), members.end(),
-                       [&address](const auto& member) { return member.mac_address != address; });
-}
-
-bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const {
-    // A Console ID is valid if it is not already taken by anybody else in the room.
-    std::lock_guard lock(member_mutex);
-    return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) {
-        return member.console_id_hash != console_id_hash;
-    });
+                       [&address](const auto& member) { return member.fake_ip != address; });
 }
 
 bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
@@ -621,19 +588,9 @@ void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
     enet_host_flush(server);
 }
 
-void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
+void Room::RoomImpl::SendIPCollision(ENetPeer* client) {
     Packet packet;
-    packet.Write(static_cast<u8>(IdMacCollision));
-
-    ENetPacket* enet_packet =
-        enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
-    enet_peer_send(client, 0, enet_packet);
-    enet_host_flush(server);
-}
-
-void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) {
-    Packet packet;
-    packet.Write(static_cast<u8>(IdConsoleIdCollision));
+    packet.Write(static_cast<u8>(IdIpCollision));
 
     ENetPacket* enet_packet =
         enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
@@ -672,20 +629,20 @@ void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
     enet_host_flush(server);
 }
 
-void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
+void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, IPv4Address fake_ip) {
     Packet packet;
     packet.Write(static_cast<u8>(IdJoinSuccess));
-    packet.Write(mac_address);
+    packet.Write(fake_ip);
     ENetPacket* enet_packet =
         enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
     enet_peer_send(client, 0, enet_packet);
     enet_host_flush(server);
 }
 
-void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) {
+void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, IPv4Address fake_ip) {
     Packet packet;
     packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
-    packet.Write(mac_address);
+    packet.Write(fake_ip);
     ENetPacket* enet_packet =
         enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
     enet_peer_send(client, 0, enet_packet);
@@ -818,7 +775,7 @@ void Room::RoomImpl::BroadcastRoomInformation() {
         std::lock_guard lock(member_mutex);
         for (const auto& member : members) {
             packet.Write(member.nickname);
-            packet.Write(member.mac_address);
+            packet.Write(member.fake_ip);
             packet.Write(member.game_info.name);
             packet.Write(member.game_info.id);
             packet.Write(member.user_data.username);
@@ -833,34 +790,43 @@ void Room::RoomImpl::BroadcastRoomInformation() {
     enet_host_flush(server);
 }
 
-MacAddress Room::RoomImpl::GenerateMacAddress() {
-    MacAddress result_mac =
-        NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI
-    std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
+IPv4Address Room::RoomImpl::GenerateFakeIPAddress() {
+    IPv4Address result_ip{192, 168, 0, 0};
+    std::uniform_int_distribution<> dis(0x01, 0xFE); // Random byte between 1 and 0xFE
     do {
-        for (std::size_t i = 3; i < result_mac.size(); ++i) {
-            result_mac[i] = dis(random_gen);
+        for (std::size_t i = 2; i < result_ip.size(); ++i) {
+            result_ip[i] = dis(random_gen);
         }
-    } while (!IsValidMacAddress(result_mac));
-    return result_mac;
+    } while (!IsValidFakeIPAddress(result_ip));
+
+    return result_ip;
 }
 
-void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
+void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
     Packet in_packet;
     in_packet.Append(event->packet->data, event->packet->dataLength);
-    in_packet.IgnoreBytes(sizeof(u8));         // Message type
-    in_packet.IgnoreBytes(sizeof(u8));         // WifiPacket Type
-    in_packet.IgnoreBytes(sizeof(u8));         // WifiPacket Channel
-    in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address
-    MacAddress destination_address;
-    in_packet.Read(destination_address);
+    in_packet.IgnoreBytes(sizeof(u8)); // Message type
+
+    in_packet.IgnoreBytes(sizeof(u8));          // Domain
+    in_packet.IgnoreBytes(sizeof(IPv4Address)); // IP
+    in_packet.IgnoreBytes(sizeof(u16));         // Port
+
+    in_packet.IgnoreBytes(sizeof(u8)); // Domain
+    IPv4Address remote_ip;
+    in_packet.Read(remote_ip);          // IP
+    in_packet.IgnoreBytes(sizeof(u16)); // Port
+
+    in_packet.IgnoreBytes(sizeof(u8)); // Protocol
+    bool broadcast;
+    in_packet.Read(broadcast); // Broadcast
 
     Packet out_packet;
     out_packet.Append(event->packet->data, event->packet->dataLength);
     ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
                                                  ENET_PACKET_FLAG_RELIABLE);
 
-    if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
+    const auto& destination_address = remote_ip;
+    if (broadcast) { // Send the data to everyone except the sender
         std::lock_guard lock(member_mutex);
         bool sent_packet = false;
         for (const auto& member : members) {
@@ -877,16 +843,16 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
         std::lock_guard lock(member_mutex);
         auto member = std::find_if(members.begin(), members.end(),
                                    [destination_address](const Member& member_entry) -> bool {
-                                       return member_entry.mac_address == destination_address;
+                                       return member_entry.fake_ip == destination_address;
                                    });
         if (member != members.end()) {
             enet_peer_send(member->peer, 0, enet_packet);
         } else {
             LOG_ERROR(Network,
-                      "Attempting to send to unknown MAC address: "
-                      "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
+                      "Attempting to send to unknown IP address: "
+                      "{}.{}.{}.{}",
                       destination_address[0], destination_address[1], destination_address[2],
-                      destination_address[3], destination_address[4], destination_address[5]);
+                      destination_address[3]);
             enet_packet_destroy(enet_packet);
         }
     }
@@ -1073,7 +1039,7 @@ std::vector<Member> Room::GetRoomMemberList() const {
         member.username = member_impl.user_data.username;
         member.display_name = member_impl.user_data.display_name;
         member.avatar_url = member_impl.user_data.avatar_url;
-        member.mac_address = member_impl.mac_address;
+        member.fake_ip = member_impl.fake_ip;
         member.game = member_impl.game_info;
         member_list.push_back(member);
     }
diff --git a/src/network/room.h b/src/network/room.h
index 6f7e3b5b53..c2a4b1a702 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -9,12 +9,12 @@
 #include <vector>
 #include "common/announce_multiplayer_room.h"
 #include "common/common_types.h"
+#include "common/socket_types.h"
 #include "network/verify_user.h"
 
 namespace Network {
 
 using AnnounceMultiplayerRoom::GameInfo;
-using AnnounceMultiplayerRoom::MacAddress;
 using AnnounceMultiplayerRoom::Member;
 using AnnounceMultiplayerRoom::RoomInformation;
 
@@ -29,12 +29,9 @@ static constexpr u32 MaxConcurrentConnections = 254;
 
 constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
 
-/// A special MAC address that tells the room we're joining to assign us a MAC address
+/// A special IP address that tells the room we're joining to assign us a IP address
 /// automatically.
-constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
-
-// 802.11 broadcast MAC address
-constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+constexpr IPv4Address NoPreferredIP = {0xFF, 0xFF, 0xFF, 0xFF};
 
 // The different types of messages that can be sent. The first byte of each packet defines the type
 enum RoomMessageTypes : u8 {
@@ -42,15 +39,14 @@ enum RoomMessageTypes : u8 {
     IdJoinSuccess,
     IdRoomInformation,
     IdSetGameInfo,
-    IdWifiPacket,
+    IdProxyPacket,
     IdChatMessage,
     IdNameCollision,
-    IdMacCollision,
+    IdIpCollision,
     IdVersionMismatch,
     IdWrongPassword,
     IdCloseRoom,
     IdRoomIsFull,
-    IdConsoleIdCollision,
     IdStatusMessage,
     IdHostKicked,
     IdHostBanned,
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index e4f823e981..9f08bf611a 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -7,6 +7,7 @@
 #include <set>
 #include <thread>
 #include "common/assert.h"
+#include "common/socket_types.h"
 #include "enet/enet.h"
 #include "network/packet.h"
 #include "network/room_member.h"
@@ -38,7 +39,7 @@ public:
     std::string username;              ///< The username of this member.
     mutable std::mutex username_mutex; ///< Mutex for locking username.
 
-    MacAddress mac_address; ///< The mac_address of this member.
+    IPv4Address fake_ip; ///< The fake ip of this member.
 
     std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
     /// Thread that receives and dispatches network packets
@@ -56,7 +57,7 @@ public:
         CallbackSet<T>& Get();
 
     private:
-        CallbackSet<WifiPacket> callback_set_wifi_packet;
+        CallbackSet<ProxyPacket> callback_set_proxy_packet;
         CallbackSet<ChatEntry> callback_set_chat_messages;
         CallbackSet<StatusMessageEntry> callback_set_status_messages;
         CallbackSet<RoomInformation> callback_set_room_information;
@@ -78,15 +79,15 @@ public:
 
     /**
      * Sends a request to the server, asking for permission to join a room with the specified
-     * nickname and preferred mac.
+     * nickname and preferred fake ip.
      * @params nickname The desired nickname.
-     * @params console_id_hash A hash of the Console ID.
-     * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
+     * @params preferred_fake_ip The preferred IP address to use in the room, the NoPreferredIP
+     * tells
      * @params password The password for the room
      * the server to assign one for us.
      */
-    void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash,
-                         const MacAddress& preferred_mac = NoPreferredMac,
+    void SendJoinRequest(const std::string& nickname_,
+                         const IPv4Address& preferred_fake_ip = NoPreferredIP,
                          const std::string& password = "", const std::string& token = "");
 
     /**
@@ -101,10 +102,10 @@ public:
     void HandleRoomInformationPacket(const ENetEvent* event);
 
     /**
-     * Extracts a WifiPacket from a received ENet packet.
+     * Extracts a ProxyPacket from a received ENet packet.
      * @param event The  ENet event that was received.
      */
-    void HandleWifiPackets(const ENetEvent* event);
+    void HandleProxyPackets(const ENetEvent* event);
 
     /**
      * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
@@ -158,12 +159,12 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
     while (IsConnected()) {
         std::lock_guard lock(network_mutex);
         ENetEvent event;
-        if (enet_host_service(client, &event, 16) > 0) {
+        if (enet_host_service(client, &event, 100) > 0) {
             switch (event.type) {
             case ENET_EVENT_TYPE_RECEIVE:
                 switch (event.packet->data[0]) {
-                case IdWifiPacket:
-                    HandleWifiPackets(&event);
+                case IdProxyPacket:
+                    HandleProxyPackets(&event);
                     break;
                 case IdChatMessage:
                     HandleChatPacket(&event);
@@ -198,13 +199,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
                     SetState(State::Idle);
                     SetError(Error::NameCollision);
                     break;
-                case IdMacCollision:
+                case IdIpCollision:
                     SetState(State::Idle);
-                    SetError(Error::MacCollision);
-                    break;
-                case IdConsoleIdCollision:
-                    SetState(State::Idle);
-                    SetError(Error::ConsoleIdCollision);
+                    SetError(Error::IpCollision);
                     break;
                 case IdVersionMismatch:
                     SetState(State::Idle);
@@ -275,15 +272,13 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
 }
 
 void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
-                                                 const std::string& console_id_hash,
-                                                 const MacAddress& preferred_mac,
+                                                 const IPv4Address& preferred_fake_ip,
                                                  const std::string& password,
                                                  const std::string& token) {
     Packet packet;
     packet.Write(static_cast<u8>(IdJoinRequest));
     packet.Write(nickname_);
-    packet.Write(console_id_hash);
-    packet.Write(preferred_mac);
+    packet.Write(preferred_fake_ip);
     packet.Write(network_version);
     packet.Write(password);
     packet.Write(token);
@@ -317,7 +312,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
 
     for (auto& member : member_information) {
         packet.Read(member.nickname);
-        packet.Read(member.mac_address);
+        packet.Read(member.fake_ip);
         packet.Read(member.game_info.name);
         packet.Read(member.game_info.id);
         packet.Read(member.username);
@@ -342,29 +337,38 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
     packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
 
     // Parse the MAC Address from the packet
-    packet.Read(mac_address);
+    packet.Read(fake_ip);
 }
 
-void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
-    WifiPacket wifi_packet{};
+void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
+    ProxyPacket proxy_packet{};
     Packet packet;
     packet.Append(event->packet->data, event->packet->dataLength);
 
     // Ignore the first byte, which is the message id.
     packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
 
-    // Parse the WifiPacket from the packet
-    u8 frame_type;
-    packet.Read(frame_type);
-    WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
+    // Parse the ProxyPacket from the packet
+    u8 local_family;
+    packet.Read(local_family);
+    proxy_packet.local_endpoint.family = static_cast<Domain>(local_family);
+    packet.Read(proxy_packet.local_endpoint.ip);
+    packet.Read(proxy_packet.local_endpoint.portno);
 
-    wifi_packet.type = type;
-    packet.Read(wifi_packet.channel);
-    packet.Read(wifi_packet.transmitter_address);
-    packet.Read(wifi_packet.destination_address);
-    packet.Read(wifi_packet.data);
+    u8 remote_family;
+    packet.Read(remote_family);
+    proxy_packet.remote_endpoint.family = static_cast<Domain>(remote_family);
+    packet.Read(proxy_packet.remote_endpoint.ip);
+    packet.Read(proxy_packet.remote_endpoint.portno);
 
-    Invoke<WifiPacket>(wifi_packet);
+    u8 protocol_type;
+    packet.Read(protocol_type);
+    proxy_packet.protocol = static_cast<Protocol>(protocol_type);
+
+    packet.Read(proxy_packet.broadcast);
+    packet.Read(proxy_packet.data);
+
+    Invoke<ProxyPacket>(proxy_packet);
 }
 
 void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
@@ -440,8 +444,8 @@ void RoomMember::RoomMemberImpl::Disconnect() {
 }
 
 template <>
-RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
-    return callback_set_wifi_packet;
+RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
+    return callback_set_proxy_packet;
 }
 
 template <>
@@ -525,19 +529,18 @@ const std::string& RoomMember::GetUsername() const {
     return room_member_impl->username;
 }
 
-const MacAddress& RoomMember::GetMacAddress() const {
-    ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
-    return room_member_impl->mac_address;
+const IPv4Address& RoomMember::GetFakeIpAddress() const {
+    ASSERT_MSG(IsConnected(), "Tried to get fake ip address while not connected");
+    return room_member_impl->fake_ip;
 }
 
 RoomInformation RoomMember::GetRoomInformation() const {
     return room_member_impl->room_information;
 }
 
-void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
-                      const char* server_addr, u16 server_port, u16 client_port,
-                      const MacAddress& preferred_mac, const std::string& password,
-                      const std::string& token) {
+void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
+                      u16 client_port, const IPv4Address& preferred_fake_ip,
+                      const std::string& password, const std::string& token) {
     // If the member is connected, kill the connection first
     if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
         Leave();
@@ -571,7 +574,7 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
     if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
         room_member_impl->nickname = nick;
         room_member_impl->StartLoop();
-        room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token);
+        room_member_impl->SendJoinRequest(nick, preferred_fake_ip, password, token);
         SendGameInfo(room_member_impl->current_game_info);
     } else {
         enet_peer_disconnect(room_member_impl->server, 0);
@@ -584,14 +587,22 @@ bool RoomMember::IsConnected() const {
     return room_member_impl->IsConnected();
 }
 
-void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) {
+void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
     Packet packet;
-    packet.Write(static_cast<u8>(IdWifiPacket));
-    packet.Write(static_cast<u8>(wifi_packet.type));
-    packet.Write(wifi_packet.channel);
-    packet.Write(wifi_packet.transmitter_address);
-    packet.Write(wifi_packet.destination_address);
-    packet.Write(wifi_packet.data);
+    packet.Write(static_cast<u8>(IdProxyPacket));
+
+    packet.Write(static_cast<u8>(proxy_packet.local_endpoint.family));
+    packet.Write(proxy_packet.local_endpoint.ip);
+    packet.Write(proxy_packet.local_endpoint.portno);
+
+    packet.Write(static_cast<u8>(proxy_packet.remote_endpoint.family));
+    packet.Write(proxy_packet.remote_endpoint.ip);
+    packet.Write(proxy_packet.remote_endpoint.portno);
+
+    packet.Write(static_cast<u8>(proxy_packet.protocol));
+    packet.Write(proxy_packet.broadcast);
+    packet.Write(proxy_packet.data);
+
     room_member_impl->Send(std::move(packet));
 }
 
@@ -645,8 +656,8 @@ RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
     return room_member_impl->Bind(callback);
 }
 
-RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
-    std::function<void(const WifiPacket&)> callback) {
+RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
+    std::function<void(const ProxyPacket&)> callback) {
     return room_member_impl->Bind(callback);
 }
 
@@ -685,7 +696,7 @@ void RoomMember::Leave() {
     room_member_impl->client = nullptr;
 }
 
-template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
+template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
 template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
 template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
 template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index bbb7d13d4c..4252b7146d 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -9,6 +9,7 @@
 #include <vector>
 #include "common/announce_multiplayer_room.h"
 #include "common/common_types.h"
+#include "common/socket_types.h"
 #include "network/room.h"
 
 namespace Network {
@@ -17,22 +18,12 @@ using AnnounceMultiplayerRoom::GameInfo;
 using AnnounceMultiplayerRoom::RoomInformation;
 
 /// Information about the received WiFi packets.
-/// Acts as our own 802.11 header.
-struct WifiPacket {
-    enum class PacketType : u8 {
-        Beacon,
-        Data,
-        Authentication,
-        AssociationResponse,
-        Deauthentication,
-        NodeMap
-    };
-    PacketType type;      ///< The type of 802.11 frame.
-    std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
-                          /// for management frames.
-    MacAddress transmitter_address; ///< Mac address of the transmitter.
-    MacAddress destination_address; ///< Mac address of the receiver.
-    u8 channel;                     ///< WiFi channel where this frame was transmitted.
+struct ProxyPacket {
+    SockAddrIn local_endpoint;
+    SockAddrIn remote_endpoint;
+    Protocol protocol;
+    bool broadcast;
+    std::vector<u8> data;
 };
 
 /// Represents a chat message.
@@ -72,15 +63,14 @@ public:
         HostKicked,     ///< Kicked by the host
 
         // Reasons why connection was rejected
-        UnknownError,       ///< Some error [permissions to network device missing or something]
-        NameCollision,      ///< Somebody is already using this name
-        MacCollision,       ///< Somebody is already using that mac-address
-        ConsoleIdCollision, ///< Somebody in the room has the same Console ID
-        WrongVersion,       ///< The room version is not the same as for this RoomMember
-        WrongPassword,      ///< The password doesn't match the one from the Room
-        CouldNotConnect,    ///< The room is not responding to a connection attempt
-        RoomIsFull,         ///< Room is already at the maximum number of players
-        HostBanned,         ///< The user is banned by the host
+        UnknownError,    ///< Some error [permissions to network device missing or something]
+        NameCollision,   ///< Somebody is already using this name
+        IpCollision,     ///< Somebody is already using that fake-ip-address
+        WrongVersion,    ///< The room version is not the same as for this RoomMember
+        WrongPassword,   ///< The password doesn't match the one from the Room
+        CouldNotConnect, ///< The room is not responding to a connection attempt
+        RoomIsFull,      ///< Room is already at the maximum number of players
+        HostBanned,      ///< The user is banned by the host
 
         // Reasons why moderation request failed
         PermissionDenied, ///< The user does not have mod permissions
@@ -92,9 +82,9 @@ public:
         std::string username;     ///< The web services username of the member. Can be empty.
         std::string display_name; ///< The web services display name of the member. Can be empty.
         std::string avatar_url;   ///< Url to the member's avatar. Can be empty.
-        GameInfo game_info;     ///< Name of the game they're currently playing, or empty if they're
-                                /// not playing anything.
-        MacAddress mac_address; ///< MAC address associated with this member.
+        GameInfo game_info;  ///< Name of the game they're currently playing, or empty if they're
+                             /// not playing anything.
+        IPv4Address fake_ip; ///< Fake Ip address associated with this member.
     };
     using MemberList = std::vector<MemberInformation>;
 
@@ -135,7 +125,7 @@ public:
     /**
      * Returns the MAC address of the RoomMember.
      */
-    const MacAddress& GetMacAddress() const;
+    const IPv4Address& GetFakeIpAddress() const;
 
     /**
      * Returns information about the room we're currently connected to.
@@ -149,19 +139,17 @@ public:
 
     /**
      * Attempts to join a room at the specified address and port, using the specified nickname.
-     * A console ID hash is passed in to check console ID conflicts.
-     * This may fail if the username or console ID is already taken.
      */
-    void Join(const std::string& nickname, const std::string& console_id_hash,
-              const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort,
-              u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
+    void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
+              u16 server_port = DefaultRoomPort, u16 client_port = 0,
+              const IPv4Address& preferred_fake_ip = NoPreferredIP,
               const std::string& password = "", const std::string& token = "");
 
     /**
      * Sends a WiFi packet to the room.
      * @param packet The WiFi packet to send.
      */
-    void SendWifiPacket(const WifiPacket& packet);
+    void SendProxyPacket(const ProxyPacket& packet);
 
     /**
      * Sends a chat message to the room.
@@ -207,14 +195,14 @@ public:
     CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
 
     /**
-     * Binds a function to an event that will be triggered every time a WifiPacket is received.
+     * Binds a function to an event that will be triggered every time a ProxyPacket is received.
      * The function wil be called everytime the event is triggered.
      * The callback function must not bind or unbind a function. Doing so will cause a deadlock
      * @param callback The function to call
      * @return A handle used for removing the function from the registered list
      */
-    CallbackHandle<WifiPacket> BindOnWifiPacketReceived(
-        std::function<void(const WifiPacket&)> callback);
+    CallbackHandle<ProxyPacket> BindOnProxyPacketReceived(
+        std::function<void(const ProxyPacket&)> callback);
 
     /**
      * Binds a function to an event that will be triggered every time the RoomInformation changes.
@@ -292,10 +280,8 @@ inline const char* GetErrorStr(const RoomMember::Error& e) {
         return "UnknownError";
     case RoomMember::Error::NameCollision:
         return "NameCollision";
-    case RoomMember::Error::MacCollision:
-        return "MaxCollision";
-    case RoomMember::Error::ConsoleIdCollision:
-        return "ConsoleIdCollision";
+    case RoomMember::Error::IpCollision:
+        return "IpCollision";
     case RoomMember::Error::WrongVersion:
         return "WrongVersion";
     case RoomMember::Error::WrongPassword:
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
index 5837b36ab5..1968a3c754 100644
--- a/src/yuzu/multiplayer/chat_room.cpp
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -316,21 +316,19 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_
 }
 
 void ChatRoom::OnSendChat() {
-    if (auto room = room_network->GetRoomMember().lock()) {
-        if (room->GetState() != Network::RoomMember::State::Joined &&
-            room->GetState() != Network::RoomMember::State::Moderator) {
-
+    if (auto room_member = room_network->GetRoomMember().lock()) {
+        if (!room_member->IsConnected()) {
             return;
         }
         auto message = ui->chat_message->text().toStdString();
         if (!ValidateMessage(message)) {
             return;
         }
-        auto nick = room->GetNickname();
-        auto username = room->GetUsername();
+        auto nick = room_member->GetNickname();
+        auto username = room_member->GetUsername();
         Network::ChatEntry chat{nick, username, message};
 
-        auto members = room->GetMemberInformation();
+        auto members = room_member->GetMemberInformation();
         auto it = std::find_if(members.begin(), members.end(),
                                [&chat](const Network::RoomMember::MemberInformation& member) {
                                    return member.nickname == chat.nickname &&
@@ -341,7 +339,7 @@ void ChatRoom::OnSendChat() {
         }
         auto player = std::distance(members.begin(), it);
         ChatMessage m(chat, *room_network);
-        room->SendChatMessage(message);
+        room_member->SendChatMessage(message);
         AppendChatMessage(m.GetPlayerChatMessage(player));
         ui->chat_message->clear();
     }
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
index a9859ed702..86baafbf03 100644
--- a/src/yuzu/multiplayer/client_room.cpp
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -74,7 +74,6 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
 void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
     if (state == Network::RoomMember::State::Joined ||
         state == Network::RoomMember::State::Moderator) {
-
         ui->chat->Clear();
         ui->chat->AppendStatusMessage(tr("Connected"));
         SetModPerms(state == Network::RoomMember::State::Moderator);
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index 9000c45312..4c0ea0a6b7 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -97,9 +97,9 @@ void DirectConnectWindow::Connect() {
     QFuture<void> f = QtConcurrent::run([&] {
         if (auto room_member = room_network.GetRoomMember().lock()) {
             auto port = UISettings::values.multiplayer_port.GetValue();
-            room_member->Join(ui->nickname->text().toStdString(), "",
-                              ui->ip->text().toStdString().c_str(), port, 0,
-                              Network::NoPreferredMac, ui->password->text().toStdString().c_str());
+            room_member->Join(ui->nickname->text().toStdString(),
+                              ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP,
+                              ui->password->text().toStdString().c_str());
         }
     });
     watcher->setFuture(f);
@@ -121,9 +121,7 @@ void DirectConnectWindow::OnConnection() {
     EndConnecting();
 
     if (auto room_member = room_network.GetRoomMember().lock()) {
-        if (room_member->GetState() == Network::RoomMember::State::Joined ||
-            room_member->GetState() == Network::RoomMember::State::Moderator) {
-
+        if (room_member->IsConnected()) {
             close();
         }
     }
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index cb9464b2bc..d70a9a3c86 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -201,8 +201,8 @@ void HostRoomWindow::Host() {
         }
 #endif
         // TODO: Check what to do with this
-        member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0,
-                     Network::NoPreferredMac, password, token);
+        member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
+                     Network::NoPreferredIP, password, token);
 
         // Store settings
         UISettings::values.multiplayer_room_nickname = ui->username->text();
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 23c2f21ab0..1cc518279c 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -169,7 +169,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
         }
 #endif
         if (auto room_member = room_network.GetRoomMember().lock()) {
-            room_member->Join(nickname, "", ip.c_str(), port, 0, Network::NoPreferredMac, password,
+            room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password,
                               token);
         }
     });
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
index 76ec276ad7..94d7a38b83 100644
--- a/src/yuzu/multiplayer/message.cpp
+++ b/src/yuzu/multiplayer/message.cpp
@@ -43,11 +43,8 @@ const ConnectionError ErrorManager::LOST_CONNECTION(
     QT_TR_NOOP("Connection to room lost. Try to reconnect."));
 const ConnectionError ErrorManager::HOST_KICKED(
     QT_TR_NOOP("You have been kicked by the room host."));
-const ConnectionError ErrorManager::MAC_COLLISION(
-    QT_TR_NOOP("MAC address is already in use. Please choose another."));
-const ConnectionError ErrorManager::CONSOLE_ID_COLLISION(QT_TR_NOOP(
-    "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation "
-    "> Configure > System to regenerate your Console ID."));
+const ConnectionError ErrorManager::IP_COLLISION(
+    QT_TR_NOOP("IP address is already in use. Please choose another."));
 const ConnectionError ErrorManager::PERMISSION_DENIED(
     QT_TR_NOOP("You do not have enough permission to perform this action."));
 const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h
index eb5c8d1be5..812495c72d 100644
--- a/src/yuzu/multiplayer/message.h
+++ b/src/yuzu/multiplayer/message.h
@@ -40,8 +40,7 @@ public:
     static const ConnectionError GENERIC_ERROR;
     static const ConnectionError LOST_CONNECTION;
     static const ConnectionError HOST_KICKED;
-    static const ConnectionError MAC_COLLISION;
-    static const ConnectionError CONSOLE_ID_COLLISION;
+    static const ConnectionError IP_COLLISION;
     static const ConnectionError PERMISSION_DENIED;
     static const ConnectionError NO_SUCH_USER;
     /**
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
index 4149b52321..84ea1aa4cb 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -95,7 +95,6 @@ void MultiplayerState::retranslateUi() {
         status_text->setText(tr("Not Connected. Click here to find a room!"));
     } else if (current_state == Network::RoomMember::State::Joined ||
                current_state == Network::RoomMember::State::Moderator) {
-
         status_text->setText(tr("Connected"));
     } else {
         status_text->setText(tr("Not Connected"));
@@ -151,11 +150,8 @@ void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
         NetworkMessage::ErrorManager::ShowError(
             NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER);
         break;
-    case Network::RoomMember::Error::MacCollision:
-        NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::MAC_COLLISION);
-        break;
-    case Network::RoomMember::Error::ConsoleIdCollision:
-        NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::CONSOLE_ID_COLLISION);
+    case Network::RoomMember::Error::IpCollision:
+        NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION);
         break;
     case Network::RoomMember::Error::RoomIsFull:
         NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 003890c077..3a0f33cba1 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -108,15 +108,11 @@ static void OnNetworkError(const Network::RoomMember::Error& error) {
             "You tried to use the same nickname as another user that is connected to the Room");
         exit(1);
         break;
-    case Network::RoomMember::Error::MacCollision:
-        LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is "
+    case Network::RoomMember::Error::IpCollision:
+        LOG_ERROR(Network, "You tried to use the same fake IP-Address as another user that is "
                            "connected to the Room");
         exit(1);
         break;
-    case Network::RoomMember::Error::ConsoleIdCollision:
-        LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room");
-        exit(1);
-        break;
     case Network::RoomMember::Error::WrongPassword:
         LOG_ERROR(Network, "Room replied with: Wrong password");
         exit(1);
@@ -365,7 +361,7 @@ int main(int argc, char** argv) {
             member->BindOnError(OnNetworkError);
             LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
                       nickname);
-            member->Join(nickname, "", address.c_str(), port, 0, Network::NoPreferredMac, password);
+            member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
         } else {
             LOG_ERROR(Network, "Could not access RoomMember");
             return 0;