From 5c9533f6d71c44f9be734a309f1d553e5b317e01 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 1 Feb 2024 18:12:05 -0600 Subject: [PATCH] Allow DSCP tagging and local traffic prioritization to be enabled separately on Mac and Linux --- src/platform/common.h | 11 +++- src/platform/linux/misc.cpp | 107 ++++++++++++++++++++++------------ src/platform/macos/misc.mm | 101 ++++++++++++++++++++++++++++++-- src/platform/windows/misc.cpp | 15 ++++- src/stream.cpp | 20 +++---- 5 files changed, 198 insertions(+), 56 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index ab0d085f..3c21e181 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -608,8 +608,17 @@ namespace platf { audio, video }; + + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type); + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging); /** * @brief Open a url in the default web browser. diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 27697c2d..2d82d387 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -586,59 +586,92 @@ namespace platf { class qos_t: public deinit_t { public: - qos_t(int sockfd, int level, int option): - sockfd(sockfd), level(level), option(option) {} + qos_t(int sockfd, std::vector> options): + sockfd(sockfd), options(options) {} virtual ~qos_t() { - int reset_val = -1; - if (setsockopt(sockfd, level, option, &reset_val, sizeof(reset_val)) < 0) { - BOOST_LOG(warning) << "Failed to reset IP TOS: "sv << errno; + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } } } private: int sockfd; - int level; - int option; + std::vector> options; }; + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { int sockfd = (int) native_socket; + std::vector> reset_options; - int level; - int option; - if (address.is_v6()) { - level = SOL_IPV6; - option = IPV6_TCLASS; + if (dscp_tagging) { + int level; + int option; + if (address.is_v6()) { + level = SOL_IPV6; + option = IPV6_TCLASS; + } + else { + level = SOL_IP; + option = IP_TOS; + } + + // The specific DSCP values here are chosen to be consistent with Windows + int dscp = 0; + switch (data_type) { + case qos_data_type_e::video: + dscp = 40; + break; + case qos_data_type_e::audio: + dscp = 56; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } + + if (dscp) { + // Shift to put the DSCP value in the correct position in the TOS field + dscp <<= 2; + + if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { + // Reset TOS to -1 when QoS is disabled + reset_options.emplace_back(std::make_tuple(level, option, -1)); + } + else { + BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; + } + } + } + + // We can use SO_PRIORITY to set outgoing traffic priority without DSCP tagging. + // + // NB: We set this after IP_TOS/IPV6_TCLASS since setting TOS value seems to + // reset SO_PRIORITY back to 0. + // + // 6 is the highest priority that can be used without SYS_CAP_ADMIN. + int priority = data_type == qos_data_type_e::audio ? 6 : 5; + if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) == 0) { + // Reset SO_PRIORITY to 0 when QoS is disabled + reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_PRIORITY, 0)); } else { - level = SOL_IP; - option = IP_TOS; + BOOST_LOG(error) << "Failed to set SO_PRIORITY: "sv << errno; } - // The specific DSCP values here are chosen to be consistent with Windows - int dscp; - switch (data_type) { - case qos_data_type_e::video: - dscp = 40; - break; - case qos_data_type_e::audio: - dscp = 56; - break; - default: - BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; - return nullptr; - } - - // Shift to put the DSCP value in the correct position in the TOS field - dscp <<= 2; - - if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) < 0) { - return nullptr; - } - - return std::make_unique(sockfd, level, option); + return std::make_unique(sockfd, reset_options); } namespace source { diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm index 436763a0..5c7fca98 100644 --- a/src/platform/macos/misc.mm +++ b/src/platform/macos/misc.mm @@ -407,12 +407,103 @@ namespace platf { return true; } + class qos_t: public deinit_t { + public: + qos_t(int sockfd, std::vector> options): + sockfd(sockfd), options(options) {} + + virtual ~qos_t() { + for (const auto &tuple : options) { + auto reset_val = std::get<2>(tuple); + if (setsockopt(sockfd, std::get<0>(tuple), std::get<1>(tuple), &reset_val, sizeof(reset_val)) < 0) { + BOOST_LOG(warning) << "Failed to reset option: "sv << errno; + } + } + } + + private: + int sockfd; + std::vector> options; + }; + + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { - // Unimplemented - // - // NB: When implementing, remember to consider that some routes can drop DSCP-tagged packets completely! - return nullptr; + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { + int sockfd = (int) native_socket; + std::vector> reset_options; + + // We can use SO_NET_SERVICE_TYPE to set link-layer prioritization without DSCP tagging + int service_type = 0; + switch (data_type) { + case qos_data_type_e::video: + service_type = NET_SERVICE_TYPE_VI; + break; + case qos_data_type_e::audio: + service_type = NET_SERVICE_TYPE_VO; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } + + if (service_type) { + if (setsockopt(sockfd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &service_type, sizeof(service_type)) == 0) { + // Reset SO_NET_SERVICE_TYPE to best-effort when QoS is disabled + reset_options.emplace_back(std::make_tuple(SOL_SOCKET, SO_NET_SERVICE_TYPE, NET_SERVICE_TYPE_BE)); + } + else { + BOOST_LOG(error) << "Failed to set SO_NET_SERVICE_TYPE: "sv << errno; + } + } + + if (dscp_tagging) { + int level; + int option; + if (address.is_v6()) { + level = IPPROTO_IPV6; + option = IPV6_TCLASS; + } + else { + level = IPPROTO_IP; + option = IP_TOS; + } + + // The specific DSCP values here are chosen to be consistent with Windows + int dscp = 0; + switch (data_type) { + case qos_data_type_e::video: + dscp = 40; + break; + case qos_data_type_e::audio: + dscp = 56; + break; + default: + BOOST_LOG(error) << "Unknown traffic type: "sv << (int) data_type; + break; + } + + if (dscp) { + // Shift to put the DSCP value in the correct position in the TOS field + dscp <<= 2; + + if (setsockopt(sockfd, level, option, &dscp, sizeof(dscp)) == 0) { + // Reset TOS to -1 when QoS is disabled + reset_options.emplace_back(std::make_tuple(level, option, -1)); + } + else { + BOOST_LOG(error) << "Failed to set TOS/TCLASS: "sv << errno; + } + } + } + + return std::make_unique(sockfd, reset_options); } } // namespace platf diff --git a/src/platform/windows/misc.cpp b/src/platform/windows/misc.cpp index 888b9e40..ec573554 100644 --- a/src/platform/windows/misc.cpp +++ b/src/platform/windows/misc.cpp @@ -1209,13 +1209,26 @@ namespace platf { QOS_FLOWID flow_id; }; + /** + * @brief Enables QoS on the given socket for traffic to the specified destination. + * @param native_socket The native socket handle. + * @param address The destination address for traffic sent on this socket. + * @param port The destination port for traffic sent on this socket. + * @param data_type The type of traffic sent on this socket. + * @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic. + */ std::unique_ptr - enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type) { + enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) { SOCKADDR_IN saddr_v4; SOCKADDR_IN6 saddr_v6; PSOCKADDR dest_addr; bool using_connect_hack = false; + // Windows doesn't support any concept of traffic priority without DSCP tagging + if (!dscp_tagging) { + return nullptr; + } + static std::once_flag load_qwave_once_flag; std::call_once(load_qwave_once_flag, []() { // qWAVE is not installed by default on Windows Server, so we load it dynamically diff --git a/src/stream.cpp b/src/stream.cpp index eefccdc7..41f80e2e 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1740,12 +1740,10 @@ namespace stream { return; } - // Enable QoS tagging on video traffic if requested by the client - if (session->config.videoQosType) { - auto address = session->video.peer.address(); - session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, - session->video.peer.port(), platf::qos_data_type_e::video); - } + // Enable local prioritization and QoS tagging on video traffic if requested by the client + auto address = session->video.peer.address(); + session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, + session->video.peer.port(), platf::qos_data_type_e::video, session->config.videoQosType != 0); BOOST_LOG(debug) << "Start capturing Video"sv; video::capture(session->mail, session->config.monitor, session); @@ -1765,12 +1763,10 @@ namespace stream { return; } - // Enable QoS tagging on audio traffic if requested by the client - if (session->config.audioQosType) { - auto address = session->audio.peer.address(); - session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, - session->audio.peer.port(), platf::qos_data_type_e::audio); - } + // Enable local prioritization and QoS tagging on audio traffic if requested by the client + auto address = session->audio.peer.address(); + session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, + session->audio.peer.port(), platf::qos_data_type_e::audio, session->config.audioQosType != 0); BOOST_LOG(debug) << "Start capturing Audio"sv; audio::capture(session->mail, session->config.audio, session);