mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-21 18:39:59 +00:00
Implement IPv6 support
This commit is contained in:
parent
7662fe8616
commit
62a5cd959a
@ -557,6 +557,30 @@ port
|
||||
|
||||
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
|
||||
^^^^
|
||||
|
||||
|
@ -511,7 +511,8 @@ namespace config {
|
||||
{}, // Password Salt
|
||||
platf::appdata().string() + "/sunshine.conf", // config file
|
||||
{}, // cmd args
|
||||
47989,
|
||||
47989, // Base port number
|
||||
"ipv4", // Address family
|
||||
platf::appdata().string() + "/sunshine.log", // log file
|
||||
{}, // prep commands
|
||||
};
|
||||
@ -1110,6 +1111,8 @@ namespace config {
|
||||
int_f(vars, "port"s, port);
|
||||
sunshine.port = (std::uint16_t) port;
|
||||
|
||||
string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv });
|
||||
|
||||
bool upnp = false;
|
||||
bool_f(vars, "upnp"s, upnp);
|
||||
|
||||
|
@ -155,6 +155,8 @@ namespace config {
|
||||
} cmd;
|
||||
|
||||
std::uint16_t port;
|
||||
std::string address_family;
|
||||
|
||||
std::string log_file;
|
||||
|
||||
std::vector<prep_cmd_t> prep_cmds;
|
||||
|
@ -76,7 +76,7 @@ namespace confighttp {
|
||||
|
||||
void
|
||||
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;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "WWW-Authenticate", R"(Basic realm="Sunshine Gamestream Host", charset="UTF-8")" }
|
||||
@ -86,7 +86,7 @@ namespace confighttp {
|
||||
|
||||
void
|
||||
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;
|
||||
const SimpleWeb::CaseInsensitiveMultimap headers {
|
||||
{ "Location", path }
|
||||
@ -96,7 +96,7 @@ namespace confighttp {
|
||||
|
||||
bool
|
||||
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);
|
||||
|
||||
if (ip_type > http::origin_web_ui_allowed) {
|
||||
@ -731,6 +731,7 @@ namespace confighttp {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
|
||||
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 };
|
||||
server.default_resource["GET"] = not_found;
|
||||
@ -758,7 +759,7 @@ namespace confighttp {
|
||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
server.resource["^/node_modules\\/.+$"]["GET"] = getNodeModules;
|
||||
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;
|
||||
|
||||
auto accept_and_run = [&](auto *server) {
|
||||
|
@ -102,12 +102,96 @@ namespace net {
|
||||
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_create(ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
||||
enet_address_set_host(&addr, "0.0.0.0");
|
||||
host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) {
|
||||
auto any_addr = net::af_to_any_address_string(af);
|
||||
enet_address_set_host(&addr, any_addr.data());
|
||||
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
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
#include "utility.h"
|
||||
@ -24,6 +26,11 @@ namespace net {
|
||||
WAN
|
||||
};
|
||||
|
||||
enum af_e : int {
|
||||
IPV4,
|
||||
BOTH
|
||||
};
|
||||
|
||||
net_e
|
||||
from_enum_string(const std::string_view &view);
|
||||
std::string_view
|
||||
@ -33,5 +40,39 @@ namespace net {
|
||||
from_address(const std::string_view &view);
|
||||
|
||||
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
|
||||
|
@ -591,7 +591,7 @@ namespace nvhttp {
|
||||
|
||||
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);
|
||||
if (ip_type > http::origin_pin_allowed) {
|
||||
BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv;
|
||||
@ -639,9 +639,24 @@ namespace nvhttp {
|
||||
tree.put("root.uniqueid", http::unique_id);
|
||||
tree.put("root.HttpsPort", map_port(PORT_HTTPS));
|
||||
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.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;
|
||||
if (video::active_hevc_mode >= 2) {
|
||||
@ -800,7 +815,7 @@ namespace nvhttp {
|
||||
rtsp_stream::launch_session_raise(launch_session);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -871,7 +886,7 @@ namespace nvhttp {
|
||||
rtsp_stream::launch_session_raise(make_launch_session(host_audio, args));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -934,6 +949,7 @@ namespace nvhttp {
|
||||
|
||||
auto port_http = map_port(PORT_HTTP);
|
||||
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];
|
||||
|
||||
@ -1026,7 +1042,7 @@ namespace nvhttp {
|
||||
https_server.resource["^/cancel$"]["GET"] = cancel;
|
||||
|
||||
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;
|
||||
|
||||
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.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;
|
||||
|
||||
auto accept_and_run = [&](auto *http_server) {
|
||||
|
@ -225,7 +225,7 @@ namespace rtsp_stream {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -233,14 +233,14 @@ namespace rtsp_stream {
|
||||
_slot_count = config::stream.channels;
|
||||
}
|
||||
|
||||
acceptor.open(tcp::v4(), ec);
|
||||
acceptor.open(af == net::IPV4 ? tcp::v4() : tcp::v6(), ec);
|
||||
if (ec) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
@ -766,7 +766,7 @@ namespace rtsp_stream {
|
||||
server.map("PLAY"sv, &cmd_play);
|
||||
|
||||
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();
|
||||
shutdown_event->raise(true);
|
||||
|
||||
|
@ -257,8 +257,8 @@ namespace stream {
|
||||
class control_server_t {
|
||||
public:
|
||||
int
|
||||
bind(std::uint16_t port) {
|
||||
_host = net::host_create(_addr, config::stream.channels, port);
|
||||
bind(net::af_e address_family, std::uint16_t port) {
|
||||
_host = net::host_create(address_family, _addr, config::stream.channels, port);
|
||||
|
||||
return !(bool) _host;
|
||||
}
|
||||
@ -1442,39 +1442,41 @@ namespace stream {
|
||||
|
||||
int
|
||||
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 video_port = map_port(VIDEO_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;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
ctx.video_sock.open(udp::v4(), ec);
|
||||
ctx.video_sock.open(protocol, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG(fatal) << "Couldn't open socket for Video server: "sv << ec.message();
|
||||
|
||||
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) {
|
||||
BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << video_port << "]: "sv << ec.message();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx.audio_sock.open(udp::v4(), ec);
|
||||
ctx.audio_sock.open(protocol, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG(fatal) << "Couldn't open socket for Audio server: "sv << ec.message();
|
||||
|
||||
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) {
|
||||
BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << audio_port << "]: "sv << ec.message();
|
||||
|
||||
|
@ -607,6 +607,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<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-->
|
||||
<div class="mb-3">
|
||||
<label for="port" class="form-label">Port</label>
|
||||
@ -1027,6 +1040,7 @@
|
||||
<script>
|
||||
// create dictionary for defaultConfig
|
||||
const defaultConfig = {
|
||||
"address_family": "ipv4",
|
||||
"always_send_scancodes": "enabled",
|
||||
"amd_coder": "auto",
|
||||
"amd_preanalysis": "disabled",
|
||||
|
Loading…
x
Reference in New Issue
Block a user