Implement IPv6 support
This commit is contained in:
@@ -557,6 +557,30 @@ port
|
|||||||
|
|
||||||
port = 47989
|
port = 47989
|
||||||
|
|
||||||
|
address_family
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
Set the address family that Sunshine will use.
|
||||||
|
|
||||||
|
.. table::
|
||||||
|
:widths: auto
|
||||||
|
|
||||||
|
===== ===========
|
||||||
|
Value Description
|
||||||
|
===== ===========
|
||||||
|
ipv4 IPv4 only
|
||||||
|
both IPv4+IPv6
|
||||||
|
===== ===========
|
||||||
|
|
||||||
|
**Default**
|
||||||
|
``ipv4``
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
address_family = both
|
||||||
|
|
||||||
pkey
|
pkey
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
|
|||||||
@@ -511,7 +511,8 @@ namespace config {
|
|||||||
{}, // Password Salt
|
{}, // Password Salt
|
||||||
platf::appdata().string() + "/sunshine.conf", // config file
|
platf::appdata().string() + "/sunshine.conf", // config file
|
||||||
{}, // cmd args
|
{}, // cmd args
|
||||||
47989,
|
47989, // Base port number
|
||||||
|
"ipv4", // Address family
|
||||||
platf::appdata().string() + "/sunshine.log", // log file
|
platf::appdata().string() + "/sunshine.log", // log file
|
||||||
{}, // prep commands
|
{}, // prep commands
|
||||||
};
|
};
|
||||||
@@ -1110,6 +1111,8 @@ namespace config {
|
|||||||
int_f(vars, "port"s, port);
|
int_f(vars, "port"s, port);
|
||||||
sunshine.port = (std::uint16_t) port;
|
sunshine.port = (std::uint16_t) port;
|
||||||
|
|
||||||
|
string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv });
|
||||||
|
|
||||||
bool upnp = false;
|
bool upnp = false;
|
||||||
bool_f(vars, "upnp"s, upnp);
|
bool_f(vars, "upnp"s, upnp);
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ namespace config {
|
|||||||
} cmd;
|
} cmd;
|
||||||
|
|
||||||
std::uint16_t port;
|
std::uint16_t port;
|
||||||
|
std::string address_family;
|
||||||
|
|
||||||
std::string log_file;
|
std::string log_file;
|
||||||
|
|
||||||
std::vector<prep_cmd_t> prep_cmds;
|
std::vector<prep_cmd_t> prep_cmds;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ namespace confighttp {
|
|||||||
|
|
||||||
void
|
void
|
||||||
send_unauthorized(resp_https_t response, req_https_t request) {
|
send_unauthorized(resp_https_t response, req_https_t request) {
|
||||||
auto address = request->remote_endpoint().address().to_string();
|
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||||
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
|
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
|
||||||
@@ -86,7 +86,7 @@ namespace confighttp {
|
|||||||
|
|
||||||
void
|
void
|
||||||
send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||||
auto address = request->remote_endpoint().address().to_string();
|
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||||
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
BOOST_LOG(info) << "Web UI: ["sv << address << "] -- not authorized"sv;
|
||||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||||
{ "Location", path }
|
{ "Location", path }
|
||||||
@@ -96,7 +96,7 @@ namespace confighttp {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
authenticate(resp_https_t response, req_https_t request) {
|
authenticate(resp_https_t response, req_https_t request) {
|
||||||
auto address = request->remote_endpoint().address().to_string();
|
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||||
auto ip_type = net::from_address(address);
|
auto ip_type = net::from_address(address);
|
||||||
|
|
||||||
if (ip_type > http::origin_web_ui_allowed) {
|
if (ip_type > http::origin_web_ui_allowed) {
|
||||||
@@ -731,6 +731,7 @@ namespace confighttp {
|
|||||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||||
|
|
||||||
auto port_https = map_port(PORT_HTTPS);
|
auto port_https = map_port(PORT_HTTPS);
|
||||||
|
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
|
||||||
|
|
||||||
https_server_t server { config::nvhttp.cert, config::nvhttp.pkey };
|
https_server_t server { config::nvhttp.cert, config::nvhttp.pkey };
|
||||||
server.default_resource["GET"] = not_found;
|
server.default_resource["GET"] = not_found;
|
||||||
@@ -758,7 +759,7 @@ namespace confighttp {
|
|||||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
||||||
server.resource["^/node_modules\\/.+$"]["GET"] = getNodeModules;
|
server.resource["^/node_modules\\/.+$"]["GET"] = getNodeModules;
|
||||||
server.config.reuse_address = true;
|
server.config.reuse_address = true;
|
||||||
server.config.address = "0.0.0.0"s;
|
server.config.address = net::af_to_any_address_string(address_family);
|
||||||
server.config.port = port_https;
|
server.config.port = port_https;
|
||||||
|
|
||||||
auto accept_and_run = [&](auto *server) {
|
auto accept_and_run = [&](auto *server) {
|
||||||
|
|||||||
@@ -102,12 +102,96 @@ namespace net {
|
|||||||
return "wan"sv;
|
return "wan"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the `af_e` enum value for the `address_family` config option value.
|
||||||
|
* @param view The config option value.
|
||||||
|
* @return The `af_e` enum value.
|
||||||
|
*/
|
||||||
|
af_e
|
||||||
|
af_from_enum_string(const std::string_view &view) {
|
||||||
|
if (view == "ipv4") {
|
||||||
|
return IPV4;
|
||||||
|
}
|
||||||
|
if (view == "both") {
|
||||||
|
return BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid warning
|
||||||
|
return BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the wildcard binding address for a given address family.
|
||||||
|
* @param af Address family.
|
||||||
|
* @return Normalized address.
|
||||||
|
*/
|
||||||
|
std::string_view
|
||||||
|
af_to_any_address_string(af_e af) {
|
||||||
|
switch (af) {
|
||||||
|
case IPV4:
|
||||||
|
return "0.0.0.0"sv;
|
||||||
|
case BOTH:
|
||||||
|
return "::"sv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid warning
|
||||||
|
return "::"sv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts an address to a normalized form.
|
||||||
|
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||||
|
* @param address The address to normalize.
|
||||||
|
* @return Normalized address.
|
||||||
|
*/
|
||||||
|
boost::asio::ip::address
|
||||||
|
normalize_address(boost::asio::ip::address address) {
|
||||||
|
// Convert IPv6-mapped IPv4 addresses into regular IPv4 addresses
|
||||||
|
if (address.is_v6()) {
|
||||||
|
auto v6 = address.to_v6();
|
||||||
|
if (v6.is_v4_mapped()) {
|
||||||
|
return boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, v6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the given address in normalized string form.
|
||||||
|
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||||
|
* @param address The address to normalize.
|
||||||
|
* @return Normalized address in string form.
|
||||||
|
*/
|
||||||
|
std::string
|
||||||
|
addr_to_normalized_string(boost::asio::ip::address address) {
|
||||||
|
return normalize_address(address).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the given address in a normalized form for in the host portion of a URL.
|
||||||
|
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||||
|
* @param address The address to normalize and escape.
|
||||||
|
* @return Normalized address in URL-escaped string.
|
||||||
|
*/
|
||||||
|
std::string
|
||||||
|
addr_to_url_escaped_string(boost::asio::ip::address address) {
|
||||||
|
address = normalize_address(address);
|
||||||
|
if (address.is_v6()) {
|
||||||
|
return "["s + address.to_string() + ']';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return address.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
host_t
|
host_t
|
||||||
host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
||||||
enet_address_set_host(&addr, "0.0.0.0");
|
auto any_addr = net::af_to_any_address_string(af);
|
||||||
|
enet_address_set_host(&addr, any_addr.data());
|
||||||
enet_address_set_port(&addr, port);
|
enet_address_set_port(&addr, port);
|
||||||
|
|
||||||
return host_t { enet_host_create(AF_INET, &addr, peers, 0, 0, 0) };
|
return host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, peers, 0, 0, 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
#include <enet/enet.h>
|
#include <enet/enet.h>
|
||||||
|
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
@@ -24,6 +26,11 @@ namespace net {
|
|||||||
WAN
|
WAN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum af_e : int {
|
||||||
|
IPV4,
|
||||||
|
BOTH
|
||||||
|
};
|
||||||
|
|
||||||
net_e
|
net_e
|
||||||
from_enum_string(const std::string_view &view);
|
from_enum_string(const std::string_view &view);
|
||||||
std::string_view
|
std::string_view
|
||||||
@@ -33,5 +40,39 @@ namespace net {
|
|||||||
from_address(const std::string_view &view);
|
from_address(const std::string_view &view);
|
||||||
|
|
||||||
host_t
|
host_t
|
||||||
host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port);
|
host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the `af_e` enum value for the `address_family` config option value.
|
||||||
|
* @param view The config option value.
|
||||||
|
* @return The `af_e` enum value.
|
||||||
|
*/
|
||||||
|
af_e
|
||||||
|
af_from_enum_string(const std::string_view &view);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the wildcard binding address for a given address family.
|
||||||
|
* @param af Address family.
|
||||||
|
* @return Normalized address.
|
||||||
|
*/
|
||||||
|
std::string_view
|
||||||
|
af_to_any_address_string(af_e af);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the given address in normalized string form.
|
||||||
|
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||||
|
* @param address The address to normalize.
|
||||||
|
* @return Normalized address in string form.
|
||||||
|
*/
|
||||||
|
std::string
|
||||||
|
addr_to_normalized_string(boost::asio::ip::address address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the given address in a normalized form for in the host portion of a URL.
|
||||||
|
* @details Normalization converts IPv4-mapped IPv6 addresses into IPv4 addresses.
|
||||||
|
* @param address The address to normalize and escape.
|
||||||
|
* @return Normalized address in URL-escaped string.
|
||||||
|
*/
|
||||||
|
std::string
|
||||||
|
addr_to_url_escaped_string(boost::asio::ip::address address);
|
||||||
} // namespace net
|
} // namespace net
|
||||||
|
|||||||
@@ -591,7 +591,7 @@ namespace nvhttp {
|
|||||||
|
|
||||||
response->close_connection_after_response = true;
|
response->close_connection_after_response = true;
|
||||||
|
|
||||||
auto address = request->remote_endpoint().address().to_string();
|
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||||
auto ip_type = net::from_address(address);
|
auto ip_type = net::from_address(address);
|
||||||
if (ip_type > http::origin_pin_allowed) {
|
if (ip_type > http::origin_pin_allowed) {
|
||||||
BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv;
|
BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv;
|
||||||
@@ -639,9 +639,24 @@ namespace nvhttp {
|
|||||||
tree.put("root.uniqueid", http::unique_id);
|
tree.put("root.uniqueid", http::unique_id);
|
||||||
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
|
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
|
||||||
tree.put("root.ExternalPort", map_port(PORT_HTTP));
|
tree.put("root.ExternalPort", map_port(PORT_HTTP));
|
||||||
tree.put("root.mac", platf::get_mac_address(local_endpoint.address().to_string()));
|
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
|
||||||
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
||||||
tree.put("root.LocalIP", local_endpoint.address().to_string());
|
|
||||||
|
// Moonlight clients track LAN IPv6 addresses separately from LocalIP which is expected to
|
||||||
|
// always be an IPv4 address. If we return that same IPv6 address here, it will clobber the
|
||||||
|
// stored LAN IPv4 address. To avoid this, we need to return an IPv4 address in this field
|
||||||
|
// when we get a request over IPv6.
|
||||||
|
//
|
||||||
|
// HACK: We should return the IPv4 address of local interface here, but we don't currently
|
||||||
|
// have that implemented. For now, we will emulate the behavior of GFE+GS-IPv6-Forwarder,
|
||||||
|
// which returns 127.0.0.1 as LocalIP for IPv6 connections. Moonlight clients with IPv6
|
||||||
|
// support know to ignore this bogus address.
|
||||||
|
if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) {
|
||||||
|
tree.put("root.LocalIP", "127.0.0.1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address()));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t codec_mode_flags = SCM_H264;
|
uint32_t codec_mode_flags = SCM_H264;
|
||||||
if (video::active_hevc_mode >= 2) {
|
if (video::active_hevc_mode >= 2) {
|
||||||
@@ -800,7 +815,7 @@ namespace nvhttp {
|
|||||||
rtsp_stream::launch_session_raise(launch_session);
|
rtsp_stream::launch_session_raise(launch_session);
|
||||||
|
|
||||||
tree.put("root.<xmlattr>.status_code", 200);
|
tree.put("root.<xmlattr>.status_code", 200);
|
||||||
tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint().address().to_string() + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||||
tree.put("root.gamesession", 1);
|
tree.put("root.gamesession", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,7 +886,7 @@ namespace nvhttp {
|
|||||||
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
|
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
|
||||||
|
|
||||||
tree.put("root.<xmlattr>.status_code", 200);
|
tree.put("root.<xmlattr>.status_code", 200);
|
||||||
tree.put("root.sessionUrl0", "rtsp://"s + request->local_endpoint().address().to_string() + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT)));
|
||||||
tree.put("root.resume", 1);
|
tree.put("root.resume", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,6 +949,7 @@ namespace nvhttp {
|
|||||||
|
|
||||||
auto port_http = map_port(PORT_HTTP);
|
auto port_http = map_port(PORT_HTTP);
|
||||||
auto port_https = map_port(PORT_HTTPS);
|
auto port_https = map_port(PORT_HTTPS);
|
||||||
|
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
|
||||||
|
|
||||||
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
|
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
|
||||||
|
|
||||||
@@ -1026,7 +1042,7 @@ namespace nvhttp {
|
|||||||
https_server.resource["^/cancel$"]["GET"] = cancel;
|
https_server.resource["^/cancel$"]["GET"] = cancel;
|
||||||
|
|
||||||
https_server.config.reuse_address = true;
|
https_server.config.reuse_address = true;
|
||||||
https_server.config.address = "0.0.0.0"s;
|
https_server.config.address = net::af_to_any_address_string(address_family);
|
||||||
https_server.config.port = port_https;
|
https_server.config.port = port_https;
|
||||||
|
|
||||||
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
|
http_server.default_resource["GET"] = not_found<SimpleWeb::HTTP>;
|
||||||
@@ -1035,7 +1051,7 @@ namespace nvhttp {
|
|||||||
http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>;
|
http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>;
|
||||||
|
|
||||||
http_server.config.reuse_address = true;
|
http_server.config.reuse_address = true;
|
||||||
http_server.config.address = "0.0.0.0"s;
|
http_server.config.address = net::af_to_any_address_string(address_family);
|
||||||
http_server.config.port = port_http;
|
http_server.config.port = port_http;
|
||||||
|
|
||||||
auto accept_and_run = [&](auto *http_server) {
|
auto accept_and_run = [&](auto *http_server) {
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ namespace rtsp_stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
bind(std::uint16_t port, boost::system::error_code &ec) {
|
bind(net::af_e af, std::uint16_t port, boost::system::error_code &ec) {
|
||||||
{
|
{
|
||||||
auto lg = _session_slots.lock();
|
auto lg = _session_slots.lock();
|
||||||
|
|
||||||
@@ -233,14 +233,14 @@ namespace rtsp_stream {
|
|||||||
_slot_count = config::stream.channels;
|
_slot_count = config::stream.channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptor.open(tcp::v4(), ec);
|
acceptor.open(af == net::IPV4 ? tcp::v4() : tcp::v6(), ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptor.set_option(boost::asio::socket_base::reuse_address { true });
|
acceptor.set_option(boost::asio::socket_base::reuse_address { true });
|
||||||
|
|
||||||
acceptor.bind(tcp::endpoint(tcp::v4(), port), ec);
|
acceptor.bind(tcp::endpoint(af == net::IPV4 ? tcp::v4() : tcp::v6(), port), ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -766,7 +766,7 @@ namespace rtsp_stream {
|
|||||||
server.map("PLAY"sv, &cmd_play);
|
server.map("PLAY"sv, &cmd_play);
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
if (server.bind(map_port(rtsp_stream::RTSP_SETUP_PORT), ec)) {
|
if (server.bind(net::af_from_enum_string(config::sunshine.address_family), map_port(rtsp_stream::RTSP_SETUP_PORT), ec)) {
|
||||||
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(rtsp_stream::RTSP_SETUP_PORT) << "], " << ec.message();
|
BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(rtsp_stream::RTSP_SETUP_PORT) << "], " << ec.message();
|
||||||
shutdown_event->raise(true);
|
shutdown_event->raise(true);
|
||||||
|
|
||||||
|
|||||||
@@ -257,8 +257,8 @@ namespace stream {
|
|||||||
class control_server_t {
|
class control_server_t {
|
||||||
public:
|
public:
|
||||||
int
|
int
|
||||||
bind(std::uint16_t port) {
|
bind(net::af_e address_family, std::uint16_t port) {
|
||||||
_host = net::host_create(_addr, config::stream.channels, port);
|
_host = net::host_create(address_family, _addr, config::stream.channels, port);
|
||||||
|
|
||||||
return !(bool) _host;
|
return !(bool) _host;
|
||||||
}
|
}
|
||||||
@@ -1442,39 +1442,41 @@ namespace stream {
|
|||||||
|
|
||||||
int
|
int
|
||||||
start_broadcast(broadcast_ctx_t &ctx) {
|
start_broadcast(broadcast_ctx_t &ctx) {
|
||||||
|
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
|
||||||
|
auto protocol = address_family == net::IPV4 ? udp::v4() : udp::v6();
|
||||||
auto control_port = map_port(CONTROL_PORT);
|
auto control_port = map_port(CONTROL_PORT);
|
||||||
auto video_port = map_port(VIDEO_STREAM_PORT);
|
auto video_port = map_port(VIDEO_STREAM_PORT);
|
||||||
auto audio_port = map_port(AUDIO_STREAM_PORT);
|
auto audio_port = map_port(AUDIO_STREAM_PORT);
|
||||||
|
|
||||||
if (ctx.control_server.bind(control_port)) {
|
if (ctx.control_server.bind(address_family, control_port)) {
|
||||||
BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << control_port << "], likely another process already bound to the port"sv;
|
BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << control_port << "], likely another process already bound to the port"sv;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
ctx.video_sock.open(udp::v4(), ec);
|
ctx.video_sock.open(protocol, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
BOOST_LOG(fatal) << "Couldn't open socket for Video server: "sv << ec.message();
|
BOOST_LOG(fatal) << "Couldn't open socket for Video server: "sv << ec.message();
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.video_sock.bind(udp::endpoint(udp::v4(), video_port), ec);
|
ctx.video_sock.bind(udp::endpoint(protocol, video_port), ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << video_port << "]: "sv << ec.message();
|
BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << video_port << "]: "sv << ec.message();
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.audio_sock.open(udp::v4(), ec);
|
ctx.audio_sock.open(protocol, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
BOOST_LOG(fatal) << "Couldn't open socket for Audio server: "sv << ec.message();
|
BOOST_LOG(fatal) << "Couldn't open socket for Audio server: "sv << ec.message();
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.audio_sock.bind(udp::endpoint(udp::v4(), audio_port), ec);
|
ctx.audio_sock.bind(udp::endpoint(protocol, audio_port), ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << audio_port << "]: "sv << ec.message();
|
BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << audio_port << "]: "sv << ec.message();
|
||||||
|
|
||||||
|
|||||||
@@ -607,6 +607,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentTab === 'advanced'" class="config-page">
|
<div v-if="currentTab === 'advanced'" class="config-page">
|
||||||
|
<!--Address family-->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="address_family" class="form-label">Address Family</label>
|
||||||
|
<select
|
||||||
|
id="address_family"
|
||||||
|
class="form-select"
|
||||||
|
v-model="config.address_family"
|
||||||
|
>
|
||||||
|
<option value="ipv4">IPv4 only</option>
|
||||||
|
<option value="both">IPv4+IPv6</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">Set the address family used by Sunshine</div>
|
||||||
|
</div>
|
||||||
<!--Port family-->
|
<!--Port family-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="port" class="form-label">Port</label>
|
<label for="port" class="form-label">Port</label>
|
||||||
@@ -1027,6 +1040,7 @@
|
|||||||
<script>
|
<script>
|
||||||
// create dictionary for defaultConfig
|
// create dictionary for defaultConfig
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
|
"address_family": "ipv4",
|
||||||
"always_send_scancodes": "enabled",
|
"always_send_scancodes": "enabled",
|
||||||
"amd_coder": "auto",
|
"amd_coder": "auto",
|
||||||
"amd_preanalysis": "disabled",
|
"amd_preanalysis": "disabled",
|
||||||
|
|||||||
Reference in New Issue
Block a user