From beb6bdfadb0c6d5aaa0c434320cd903b6e0c85cd Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 30 Jun 2021 15:25:08 +0200 Subject: [PATCH] Allow end user to configure what ports to listen on --- assets/sunshine.conf | 13 +++++- assets/web/config.html | 37 +++++++++++++---- sunshine/config.cpp | 12 +++++- sunshine/config.h | 1 + sunshine/confighttp.cpp | 19 ++++----- sunshine/confighttp.h | 3 +- sunshine/httpcommon.cpp | 7 +++- sunshine/httpcommon.h | 1 + sunshine/main.cpp | 4 ++ sunshine/main.h | 2 + sunshine/nvhttp.cpp | 13 +++--- sunshine/nvhttp.h | 4 +- sunshine/platform/linux/publish.cpp | 11 +++++- sunshine/platform/windows/publish.cpp | 2 +- sunshine/rtsp.cpp | 6 +-- sunshine/rtsp.h | 2 + sunshine/stream.cpp | 20 +++++----- sunshine/stream.h | 4 ++ sunshine/upnp.cpp | 57 ++++++++++++++++----------- 19 files changed, 149 insertions(+), 69 deletions(-) diff --git a/assets/sunshine.conf b/assets/sunshine.conf index 87fc3d29..f7c0fcd6 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -2,7 +2,7 @@ # external_ip = 123.456.789.12 # Set the familly of ports used by Sunshine -# port = 47984 +# port = 47989 # The private key must be 2048 bits # pkey = /dir/pkey.pem @@ -35,7 +35,16 @@ # lan: Only those in LAN may access /pin # wan: Anyone may access /pin # -# origin_pin_allowed = lan +# origin_pin_allowed = pc + +# The origin of the remote endpoint address that is not denied for HTTPS Web UI +# Could be any of the following values: +# pc|lan|wan +# pc: Only localhost may access the Web Manager +# lan: Only those in LAN may access the Web Manager +# wan: Anyone may access the Web Manager +# +# origin_web_ui_allowed = lan # If UPnP is enabled, Sunshine will attempt to open ports for streaming over the internet # To enable it, uncomment the following line: diff --git a/assets/web/config.html b/assets/web/config.html index 23ac5d31..8002ef45 100644 --- a/assets/web/config.html +++ b/assets/web/config.html @@ -32,15 +32,15 @@
The minimum log level printed to standard out
- +
- - + + + -
The origin of the remote endpoint address that is not denied for HTTP method /pin +
The origin of the remote endpoint address that is not denied access to Web UI
@@ -238,6 +238,15 @@
+ +
+ + +
+ Set the familly of ports used by Sunshine +
+
@@ -341,6 +350,17 @@ Store Username/Password seperately from Sunshine's state file.
+ +
+ + +
The origin of the remote endpoint address that is not denied for HTTP method /pin +
+
@@ -517,7 +537,8 @@ //Populate default values if not present in config this.config.upnp = this.config.upnp || 'disabled'; this.config.min_log_level = this.config.min_log_level || 2; - this.config.origin_pin_allowed = this.config.origin_pin_allowed || "lan"; + this.config.origin_pin_allowed = this.config.origin_pin_allowed || "pc"; + this.config.origin_web_ui_allowed = this.config.origin_web_manager_allowed || "lan"; this.config.hevc_mode = this.config.hevc_mode || 0; this.config.encoder = this.config.encoder || ''; this.config.nv_preset = this.config.nv_preset || 'default'; diff --git a/sunshine/config.cpp b/sunshine/config.cpp index f02da2aa..ba6f75fc 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -185,7 +185,9 @@ stream_t stream { }; nvhttp_t nvhttp { - "lan", // origin_pin + "pc", // origin_pin + "lan", // origin web manager + PRIVATE_KEY_FILE, CERTIFICATE_FILE, @@ -222,7 +224,8 @@ sunshine_t sunshine { {}, // Password {}, // Password Salt SUNSHINE_ASSETS_DIR "/sunshine.conf", // config file - {} // cmd args + {}, // cmd args + 47989, }; bool endline(char ch) { @@ -610,6 +613,7 @@ void apply_config(std::unordered_map &&vars) { string_f(vars, "virtual_sink", audio.virtual_sink); string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv }); + string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv }); int to = -1; int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits::max() }); @@ -642,6 +646,10 @@ void apply_config(std::unordered_map &&vars) { input.key_repeat_delay = std::chrono::milliseconds { to }; } + int port = sunshine.port; + int_f(vars, "port"s, port); + sunshine.port = (std::uint16_t)port; + bool upnp = false; bool_f(vars, "upnp"s, upnp); diff --git a/sunshine/config.h b/sunshine/config.h index 59a0ebdc..bae3178a 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -59,6 +59,7 @@ struct nvhttp_t { // Could be any of the following values: // pc|lan|wan std::string origin_pin_allowed; + std::string origin_web_ui_allowed; std::string pkey; // must be 2048 bits std::string cert; // must be signed with a key of 2048 bits diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index f568b693..b21c7571 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -30,10 +30,9 @@ #include "utility.h" #include "uuid.h" -namespace confighttp { using namespace std::literals; -constexpr auto PORT_HTTPS = 47990; +namespace confighttp { namespace fs = std::filesystem; namespace pt = boost::property_tree; @@ -67,7 +66,7 @@ void print_req(const req_https_t &request) { void send_unauthorized(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); - BOOST_LOG(info) << '[' << address << "] -- denied"sv; + 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")" } }; @@ -78,8 +77,8 @@ bool authenticate(resp_https_t response, req_https_t request) { auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); - if(ip_type > http::origin_pin_allowed) { - BOOST_LOG(info) << '[' << address << "] -- denied"sv; + if(ip_type > http::origin_web_ui_allowed) { + BOOST_LOG(info) << "Web UI: ["sv << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); return false; } @@ -455,6 +454,8 @@ void savePin(resp_https_t response, req_https_t request) { void start() { auto shutdown_event = mail::man->event(mail::shutdown); + auto port_https = map_port(PORT_HTTPS); + auto ctx = std::make_shared(boost::asio::ssl::context::tls); ctx->use_certificate_chain_file(config::nvhttp.cert); ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); @@ -475,14 +476,14 @@ void start() { server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; server.config.reuse_address = true; server.config.address = "0.0.0.0"s; - server.config.port = PORT_HTTPS; + server.config.port = port_https; try { server.bind(); - BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTPS << "]"; + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << port_https << "]"; } catch(boost::system::system_error &err) { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_https << "]: "sv << err.what(); shutdown_event->raise(true); return; @@ -497,7 +498,7 @@ void start() { return; } - BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTPS << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start Configuration HTTPS server to port ["sv << port_https << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/sunshine/confighttp.h b/sunshine/confighttp.h index 25507503..1e0c4a76 100644 --- a/sunshine/confighttp.h +++ b/sunshine/confighttp.h @@ -14,7 +14,8 @@ namespace confighttp { +constexpr auto PORT_HTTPS = 1; void start(); -} +} // namespace confighttp #endif //SUNSHINE_CONFIGHTTP_H diff --git a/sunshine/httpcommon.cpp b/sunshine/httpcommon.cpp index 5f796595..1f3b1dc5 100644 --- a/sunshine/httpcommon.cpp +++ b/sunshine/httpcommon.cpp @@ -35,10 +35,13 @@ bool user_creds_exist(const std::string &file); std::string unique_id; net::net_e origin_pin_allowed; +net::net_e origin_web_ui_allowed; int init() { - bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; + origin_pin_allowed = net::from_enum_string(config::nvhttp.origin_pin_allowed); + origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed); + if(clean_slate) { unique_id = util::uuid_t::generate().string(); auto dir = std::filesystem::temp_directory_path() / "Sushine"sv; diff --git a/sunshine/httpcommon.h b/sunshine/httpcommon.h index fbd43253..37d8451f 100644 --- a/sunshine/httpcommon.h +++ b/sunshine/httpcommon.h @@ -14,5 +14,6 @@ int save_user_creds( int reload_user_creds(const std::string &file); extern std::string unique_id; extern net::net_e origin_pin_allowed; +extern net::net_e origin_web_ui_allowed; } // namespace http \ No newline at end of file diff --git a/sunshine/main.cpp b/sunshine/main.cpp index d602c192..5b178360 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -270,4 +270,8 @@ int write_file(const char *path, const std::string_view &contents) { out << contents; return 0; +} + +std::uint16_t map_port(int port) { + return (std::uint16_t)((int)config::sunshine.port + port); } \ No newline at end of file diff --git a/sunshine/main.h b/sunshine/main.h index 7de488ab..e0023586 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -29,6 +29,8 @@ void print_help(const char *name); std::string read_file(const char *path); int write_file(const char *path, const std::string_view &contents); +std::uint16_t map_port(int port); + namespace mail { #define MAIL(x) \ constexpr auto x = std::string_view { #x } diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index cc7b6a07..3f8e2895 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -479,7 +479,7 @@ void pin(std::shared_ptr::Response> response, auto address = request->remote_endpoint_address(); auto ip_type = net::from_address(address); if(ip_type > http::origin_pin_allowed) { - BOOST_LOG(info) << '[' << address << "] -- denied"sv; + BOOST_LOG(info) << "/pin: ["sv << address << "] -- denied"sv; response->write(SimpleWeb::StatusCode::client_error_forbidden); @@ -759,6 +759,9 @@ void appasset(resp_https_t response, req_https_t request) { void start() { auto shutdown_event = mail::man->event(mail::shutdown); + auto port_http = map_port(PORT_HTTP); + auto port_https = map_port(PORT_HTTPS); + bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; if(!clean_slate) { @@ -836,7 +839,7 @@ void start() { https_server.config.reuse_address = true; https_server.config.address = "0.0.0.0"s; - https_server.config.port = PORT_HTTPS; + https_server.config.port = port_https; http_server.default_resource = not_found; http_server.resource["^/serverinfo$"]["GET"] = serverinfo; @@ -845,14 +848,14 @@ void start() { http_server.config.reuse_address = true; http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; + http_server.config.port = port_http; try { https_server.bind(); http_server.bind(); } catch(boost::system::system_error &err) { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << port_http << ", "sv << port_http << "]: "sv << err.what(); shutdown_event->raise(true); return; @@ -868,7 +871,7 @@ void start() { return; } - BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + BOOST_LOG(fatal) << "Couldn't start http server to ports ["sv << port_https << ", "sv << port_https << "]: "sv << err.what(); shutdown_event->raise(true); return; } diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index 9e51171a..f6d1afeb 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -9,8 +9,8 @@ #include namespace nvhttp { -constexpr auto PORT_HTTP = 47989; -constexpr auto PORT_HTTPS = 47984; +constexpr auto PORT_HTTP = 0; +constexpr auto PORT_HTTPS = -5; void start(); bool pin(std::string pin); diff --git a/sunshine/platform/linux/publish.cpp b/sunshine/platform/linux/publish.cpp index f63e0881..05208cbb 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/sunshine/platform/linux/publish.cpp @@ -328,7 +328,16 @@ void create_services(avahi::Client *c) { if(avahi::entry_group_is_empty(group)) { BOOST_LOG(info) << "Adding avahi service "sv << name.get(); - ret = avahi::entry_group_add_service(group, avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, avahi::PublishFlags(0), name.get(), SERVICE_TYPE, nullptr, nullptr, nvhttp::PORT_HTTP, nullptr); + ret = avahi::entry_group_add_service( + group, + avahi::IF_UNSPEC, avahi::PROTO_UNSPEC, + avahi::PublishFlags(0), + name.get(), + SERVICE_TYPE, + nullptr, nullptr, + map_port(nvhttp::PORT_HTTP), + nullptr); + if(ret < 0) { if(ret == avahi::ERR_COLLISION) { // A service name collision with a local service happened. Let's pick a new name diff --git a/sunshine/platform/windows/publish.cpp b/sunshine/platform/windows/publish.cpp index 7a0911b9..2d4888d2 100644 --- a/sunshine/platform/windows/publish.cpp +++ b/sunshine/platform/windows/publish.cpp @@ -142,7 +142,7 @@ static int service(bool enable) { DNS_SERVICE_INSTANCE instance {}; instance.pszInstanceName = name.data(); - instance.wPort = nvhttp::PORT_HTTP; + instance.wPort = map_port(nvhttp::PORT_HTTP); instance.pszHostName = host.data(); DNS_SERVICE_REGISTER_REQUEST req {}; diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 89197de3..538811cd 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -31,8 +31,6 @@ std::string to_string(T &&t) { return ss.str(); } -constexpr auto RTSP_SETUP_PORT = 48010; - void free_msg(PRTSP_MESSAGE msg) { freeMessage(msg); @@ -505,8 +503,8 @@ void rtpThread() { server.map("PLAY"sv, &cmd_play); - if(server.bind(RTSP_SETUP_PORT)) { - BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << RTSP_SETUP_PORT << "], likely another process already bound to the port"sv; + if(server.bind(map_port(RTSP_SETUP_PORT))) { + BOOST_LOG(fatal) << "Couldn't bind RTSP server to port ["sv << map_port(RTSP_SETUP_PORT) << "], likely another process already bound to the port"sv; shutdown_event->raise(true); return; diff --git a/sunshine/rtsp.h b/sunshine/rtsp.h index 64d3cc85..f038a5cf 100644 --- a/sunshine/rtsp.h +++ b/sunshine/rtsp.h @@ -11,6 +11,8 @@ #include "thread_safe.h" namespace stream { +constexpr auto RTSP_SETUP_PORT = 21; + struct launch_session_t { crypto::aes_t gcm_key; crypto::aes_t iv; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index eedd7328..e3488a77 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -44,10 +44,6 @@ static const short packetTypes[] = { 0x0100, // Termination }; -constexpr auto VIDEO_STREAM_PORT = 47998; -constexpr auto CONTROL_PORT = 47999; -constexpr auto AUDIO_STREAM_PORT = 48000; - namespace asio = boost::asio; namespace sys = boost::system; @@ -730,8 +726,12 @@ void audioBroadcastThread(udp::socket &sock) { } int start_broadcast(broadcast_ctx_t &ctx) { - if(ctx.control_server.bind(CONTROL_PORT)) { - BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << CONTROL_PORT << "], likely another process already bound to the port"sv; + 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)) { + BOOST_LOG(error) << "Couldn't bind Control server to port ["sv << control_port << "], likely another process already bound to the port"sv; return -1; } @@ -744,9 +744,9 @@ int start_broadcast(broadcast_ctx_t &ctx) { return -1; } - ctx.video_sock.bind(udp::endpoint(udp::v4(), VIDEO_STREAM_PORT), ec); + ctx.video_sock.bind(udp::endpoint(udp::v4(), video_port), ec); if(ec) { - BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << VIDEO_STREAM_PORT << "]: "sv << ec.message(); + BOOST_LOG(fatal) << "Couldn't bind Video server to port ["sv << video_port << "]: "sv << ec.message(); return -1; } @@ -758,9 +758,9 @@ int start_broadcast(broadcast_ctx_t &ctx) { return -1; } - ctx.audio_sock.bind(udp::endpoint(udp::v4(), AUDIO_STREAM_PORT), ec); + ctx.audio_sock.bind(udp::endpoint(udp::v4(), audio_port), ec); if(ec) { - BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << AUDIO_STREAM_PORT << "]: "sv << ec.message(); + BOOST_LOG(fatal) << "Couldn't bind Audio server to port ["sv << audio_port << "]: "sv << ec.message(); return -1; } diff --git a/sunshine/stream.h b/sunshine/stream.h index ed526ce8..226929a6 100644 --- a/sunshine/stream.h +++ b/sunshine/stream.h @@ -12,6 +12,10 @@ #include "video.h" namespace stream { +constexpr auto VIDEO_STREAM_PORT = 9; +constexpr auto CONTROL_PORT = 10; +constexpr auto AUDIO_STREAM_PORT = 11; + struct session_t; struct config_t { audio::config_t audio; diff --git a/sunshine/upnp.cpp b/sunshine/upnp.cpp index 117a5615..ad17e880 100644 --- a/sunshine/upnp.cpp +++ b/sunshine/upnp.cpp @@ -2,14 +2,19 @@ #include #include "config.h" +#include "confighttp.h" #include "main.h" +#include "network.h" +#include "nvhttp.h" +#include "rtsp.h" +#include "stream.h" #include "upnp.h" #include "utility.h" using namespace std::literals; namespace upnp { -constexpr auto INET6_ADDRSTRLEN = 46; +constexpr auto INET6_ADDRESS_STRLEN = 46; constexpr auto IPv4 = 0; constexpr auto IPv6 = 1; @@ -21,9 +26,6 @@ KITTY_USING_MOVE_T(urls_t, UPNPUrls, , { }); struct mapping_t { - mapping_t(std::string &&wan, std::string &&lan, std::string &&description, bool tcp) - : port { std::move(wan), std::move(lan) }, description { std::move(description) }, tcp { tcp } {} - struct { std::string wan; std::string lan; @@ -33,16 +35,6 @@ struct mapping_t { bool tcp; }; -static const std::vector mappings { - { "48010"s, "48010"s, "RTSP setup port"s, false }, - { "47998"s, "47998"s, "Video stream port"s, false }, - { "47999"s, "47998"s, "Control stream port"s, false }, - { "48000"s, "48000"s, "Audio stream port"s, false }, - { "47989"s, "47989"s, "Gamestream http port"s, true }, - { "47984"s, "47984"s, "Gamestream https port"s, true }, - { "47990"s, "47990"s, "Sunshine Web Manager port"s, true }, -}; - void unmap( const urls_t &urls, const IGDdatas &data, @@ -69,19 +61,18 @@ void unmap( class deinit_t : public platf::deinit_t { public: using iter_t = std::vector::const_reverse_iterator; - deinit_t(urls_t &&urls, IGDdatas data, iter_t begin, iter_t end) - : urls { std::move(urls) }, data { data }, begin { begin }, end { end } {} + deinit_t(urls_t &&urls, IGDdatas data, std::vector &&mapping) + : urls { std::move(urls) }, data { data }, mapping { std::move(mapping) } {} ~deinit_t() { BOOST_LOG(info) << "Unmapping UPNP ports..."sv; - unmap(urls, data, begin, end); + unmap(urls, data, std::rbegin(mapping), std::rend(mapping)); } urls_t urls; IGDdatas data; - iter_t begin; - iter_t end; + std::vector mapping; }; static std::string_view status_string(int status) { @@ -111,8 +102,8 @@ std::unique_ptr start() { BOOST_LOG(debug) << "Found device: "sv << dev->descURL; } - std::array lan_addr; - std::array wan_addr; + std::array lan_addr; + std::array wan_addr; urls_t urls; IGDdatas data; @@ -139,6 +130,28 @@ std::unique_ptr start() { return nullptr; } + auto rtsp = std::to_string(map_port(stream::RTSP_SETUP_PORT)); + auto video = std::to_string(map_port(stream::VIDEO_STREAM_PORT)); + auto audio = std::to_string(map_port(stream::AUDIO_STREAM_PORT)); + auto control = std::to_string(map_port(stream::CONTROL_PORT)); + auto gs_http = std::to_string(map_port(nvhttp::PORT_HTTP)); + auto gs_https = std::to_string(map_port(nvhttp::PORT_HTTPS)); + auto wm_http = std::to_string(map_port(confighttp::PORT_HTTPS)); + + std::vector mappings { + { rtsp, rtsp, "RTSP setup port"s, false }, + { video, video, "Video stream port"s, false }, + { audio, audio, "Control stream port"s, false }, + { control, control, "Audio stream port"s, false }, + { gs_http, gs_http, "Gamestream http port"s, true }, + { gs_https, gs_https, "Gamestream https port"s, true }, + }; + + // Only map port for the Web Manager if it is configured to accept connection from WAN + if(net::from_enum_string(config::nvhttp.origin_web_ui_allowed) > net::LAN) { + mappings.emplace_back(mapping_t { wm_http, wm_http, "Sunshine Web UI port"s, true }); + } + auto it = std::begin(mappings); status = 0; @@ -166,6 +179,6 @@ std::unique_ptr start() { return nullptr; } - return std::make_unique(std::move(urls), data, std::rbegin(mappings), std::rend(mappings)); + return std::make_unique(std::move(urls), data, std::move(mappings)); } } // namespace upnp \ No newline at end of file