From f03fcb125eadc0fe0ad9011fa65708e5885a22da Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 11 Feb 2021 14:29:08 +0100 Subject: [PATCH 1/3] librtmp: Add interface binding for Linux librtmp: Make log message more accurate --- plugins/obs-outputs/librtmp/rtmp.c | 19 ++++++++++++++++++- plugins/obs-outputs/librtmp/rtmp.h | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/obs-outputs/librtmp/rtmp.c b/plugins/obs-outputs/librtmp/rtmp.c index abce64518b00b..e94c68beb8de0 100644 --- a/plugins/obs-outputs/librtmp/rtmp.c +++ b/plugins/obs-outputs/librtmp/rtmp.c @@ -877,13 +877,30 @@ RTMP_Connect0(RTMP *r, struct sockaddr * service, socklen_t addrlen) #ifdef SO_NOSIGPIPE setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof(int)); #endif +#endif + +#ifdef __linux__ + if(r->m_bindInterface.av_len) + { + if (setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_BINDTODEVICE, + r->m_bindInterface.av_val, + r->m_bindInterface.av_len) < 0) + { + int err = GetSockError(); + RTMP_Log(RTMP_LOGERROR, "%s, failed to bind socket to interface: %s (%d)", + __FUNCTION__, socketerror(err), err); + r->last_error_code = err; + RTMP_Close(r); + return FALSE; + } + } #endif if(r->m_bindIP.addrLen) { if (bind(r->m_sb.sb_socket, (const struct sockaddr *)&r->m_bindIP.addr, r->m_bindIP.addrLen) < 0) { int err = GetSockError(); - RTMP_Log(RTMP_LOGERROR, "%s, failed to bind socket: %s (%d)", + RTMP_Log(RTMP_LOGERROR, "%s, failed to bind socket to address: %s (%d)", __FUNCTION__, socketerror(err), err); r->last_error_code = err; RTMP_Close(r); diff --git a/plugins/obs-outputs/librtmp/rtmp.h b/plugins/obs-outputs/librtmp/rtmp.h index b1c48e2f3a9f6..a900efbe17a0d 100644 --- a/plugins/obs-outputs/librtmp/rtmp.h +++ b/plugins/obs-outputs/librtmp/rtmp.h @@ -420,6 +420,7 @@ extern "C" void* m_customSendParam; CUSTOMSEND m_customSendFunc; + AVal m_bindInterface; RTMP_BINDINFO m_bindIP; uint8_t m_bSendChunkSizeInfo; From e7a2ca4cb8e28a0c4c08fb00e23e065627580201 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 11 Feb 2021 14:47:45 +0100 Subject: [PATCH 2/3] obs-outputs: Add RTMP iface binding for Linux --- plugins/obs-outputs/net-if.c | 32 +++++++++++++++++++++++++++++ plugins/obs-outputs/net-if.h | 15 ++++++++++++++ plugins/obs-outputs/rtmp-stream.c | 34 +++++++++++++++++++++++++++++++ plugins/obs-outputs/rtmp-stream.h | 2 ++ 4 files changed, 83 insertions(+) diff --git a/plugins/obs-outputs/net-if.c b/plugins/obs-outputs/net-if.c index 29bb57d6a42bc..1587be47887b8 100644 --- a/plugins/obs-outputs/net-if.c +++ b/plugins/obs-outputs/net-if.c @@ -139,6 +139,38 @@ static inline bool is_loopback(struct ifaddrs *ifa) return n && (strcmp(n, "lo") == 0 || strcmp(n, "lo0") == 0); } +#ifdef __linux__ +void netif_get_ifaces(struct netif_siface_data *ifaces) +{ + da_init(ifaces->ifaces); + + struct ifaddrs *ifaddr, *ifa; + unsigned int family; + + if (getifaddrs(&ifaddr) == -1) { + warn("getifaddrs() failed"); + return; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || is_loopback(ifa)) + continue; + family = ifa->ifa_addr->sa_family; + + if ((family == AF_INET) || (family == AF_INET6)) { + char *item; + char *iface_dup = bstrdup(ifa->ifa_name); + + item = iface_dup; + + da_push_back(ifaces->ifaces, &item); + } + } + + freeifaddrs(ifaddr); +} +#endif + static inline void netif_get_addrs_nix(struct netif_saddr_data *ifaddrs) { struct ifaddrs *ifaddr, *ifa; diff --git a/plugins/obs-outputs/net-if.h b/plugins/obs-outputs/net-if.h index 757ee00e82781..6b045666cfba6 100644 --- a/plugins/obs-outputs/net-if.h +++ b/plugins/obs-outputs/net-if.h @@ -53,6 +53,21 @@ #endif +#ifdef __linux__ +struct netif_siface_data { + DARRAY(char *) ifaces; +}; + +static inline void netif_siface_data_free(struct netif_siface_data *data) +{ + for (size_t i = 0; i < data->ifaces.num; i++) + bfree(data->ifaces.array[i]); + da_free(data->ifaces); +} + +extern void netif_get_ifaces(struct netif_siface_data *ifaces); +#endif + struct netif_saddr_item { char *name; char *addr; diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 8b1908f568f8e..67a5ed19f7cc0 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -125,6 +125,7 @@ static void rtmp_stream_destroy(void *data) dstr_free(&stream->username); dstr_free(&stream->password); dstr_free(&stream->encoder_name); + dstr_free(&stream->bind_interface); dstr_free(&stream->bind_ip); os_event_destroy(stream->stop_event); os_sem_destroy(stream->send_sem); @@ -1067,6 +1068,17 @@ static int try_connect(struct rtmp_stream *stream) stream->rtmp.Link.swfUrl = stream->rtmp.Link.tcUrl; stream->rtmp.Link.customConnectEncode = add_connect_data; + if (dstr_is_empty(&stream->bind_interface) || + dstr_cmp(&stream->bind_interface, "default") == 0) { + memset(&stream->rtmp.m_bindInterface, 0, + sizeof(stream->rtmp.m_bindInterface)); + } else { + set_rtmp_dstr(&stream->rtmp.m_bindInterface, + &stream->bind_interface); + info("Binding to interface %s", + stream->rtmp.m_bindInterface.av_val); + } + if (dstr_is_empty(&stream->bind_ip) || dstr_cmp(&stream->bind_ip, "default") == 0) { memset(&stream->rtmp.m_bindIP, 0, @@ -1109,6 +1121,7 @@ static bool init_connect(struct rtmp_stream *stream) { obs_service_t *service; obs_data_t *settings; + const char *bind_interface; const char *bind_ip; int64_t drop_p; int64_t drop_b; @@ -1180,6 +1193,9 @@ static bool init_connect(struct rtmp_stream *stream) stream->drop_threshold_usec = 1000 * drop_b; stream->pframe_drop_threshold_usec = 1000 * drop_p; + bind_interface = obs_data_get_string(settings, OPT_BIND_INTERFACE); + dstr_copy(&stream->bind_interface, bind_interface); + bind_ip = obs_data_get_string(settings, OPT_BIND_IP); dstr_copy(&stream->bind_ip, bind_ip); @@ -1568,6 +1584,9 @@ static obs_properties_t *rtmp_stream_properties(void *unused) UNUSED_PARAMETER(unused); obs_properties_t *props = obs_properties_create(); +#ifdef __linux__ + struct netif_siface_data ifaces = {0}; +#endif struct netif_saddr_data addrs = {0}; obs_property_t *p; @@ -1575,6 +1594,21 @@ static obs_properties_t *rtmp_stream_properties(void *unused) obs_module_text("RTMPStream.DropThreshold"), 200, 10000, 100); obs_property_int_set_suffix(p, " ms"); +#ifdef __linux__ + p = obs_properties_add_list(props, OPT_BIND_INTERFACE, + obs_module_text("RTMPStream.BindInterface"), + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + + obs_property_list_add_string(p, obs_module_text("Default"), "default"); + + netif_get_ifaces(&ifaces); + for (size_t i = 0; i < ifaces.ifaces.num; i++) { + char *item = ifaces.ifaces.array[i]; + obs_property_list_add_string(p, item, item); + } + netif_siface_data_free(&ifaces); +#endif p = obs_properties_add_list(props, OPT_BIND_IP, obs_module_text("RTMPStream.BindIP"), diff --git a/plugins/obs-outputs/rtmp-stream.h b/plugins/obs-outputs/rtmp-stream.h index bca991909d42a..474faab3ee7a0 100644 --- a/plugins/obs-outputs/rtmp-stream.h +++ b/plugins/obs-outputs/rtmp-stream.h @@ -28,6 +28,7 @@ #define OPT_DROP_THRESHOLD "drop_threshold_ms" #define OPT_PFRAME_DROP_THRESHOLD "pframe_drop_threshold_ms" #define OPT_MAX_SHUTDOWN_TIME_SEC "max_shutdown_time_sec" +#define OPT_BIND_INTERFACE "bind_interface" #define OPT_BIND_IP "bind_ip" #define OPT_NEWSOCKETLOOP_ENABLED "new_socket_loop_enabled" #define OPT_LOWLATENCY_ENABLED "low_latency_mode_enabled" @@ -82,6 +83,7 @@ struct rtmp_stream { struct dstr path, key; struct dstr username, password; struct dstr encoder_name; + struct dstr bind_interface; struct dstr bind_ip; /* frame drop variables */ From adee410af06c7ad837fdcb87da9303097599e947 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 11 Feb 2021 14:51:42 +0100 Subject: [PATCH 3/3] UI: Add interface binding property for Linux --- UI/data/locale/en-US.ini | 1 + UI/forms/OBSBasicSettings.ui | 24 ++++++-- UI/window-basic-auto-config-test.cpp | 3 + UI/window-basic-main-outputs.cpp | 6 ++ UI/window-basic-main.cpp | 2 + UI/window-basic-settings.cpp | 91 +++++++++++++++++++++++++++- UI/window-basic-settings.hpp | 3 + 7 files changed, 123 insertions(+), 7 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index e66cb30f31d90..34f19ff59f6cb 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -1174,6 +1174,7 @@ Basic.Settings.Advanced.StreamDelay.Preserve="Preserve cutoff point (increase de Basic.Settings.Advanced.StreamDelay.MemoryUsage="Estimated Memory Usage: %1 MB" Basic.Settings.Advanced.Network="Network" Basic.Settings.Advanced.Network.Disabled="The currently selected streaming protocol does not support changing network settings." +Basic.Settings.Advanced.Network.BindToInterface="Bind to interface" Basic.Settings.Advanced.Network.BindToIP="Bind to IP" Basic.Settings.Advanced.Network.EnableNewSocketLoop="Enable network optimizations" Basic.Settings.Advanced.Network.EnableLowLatencyMode="Enable TCP pacing" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index ff27f52c25533..f626dfb903a12 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -7315,6 +7315,19 @@ + + + Basic.Settings.Advanced.Network.BindToInterface + + + bindToInterface + + + + + + + Basic.Settings.Advanced.Network.BindToIP @@ -7324,17 +7337,17 @@ - + - + Basic.Settings.Advanced.Network.EnableNewSocketLoop - + false @@ -7344,7 +7357,7 @@ - + Qt::Horizontal @@ -7357,7 +7370,7 @@ - + Basic.Settings.Output.DynamicBitrate.TT @@ -7679,6 +7692,7 @@ reconnectRetryDelay reconnectMaxRetries bindToIP + bindToInterface dynBitrate enableNewSocketLoop enableLowLatencyMode diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index bb2a86a538705..5510502911ea4 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -228,6 +228,9 @@ void AutoConfigTestPage::TestBandwidthThread() obs_data_set_int(aencoder_settings, "bitrate", 32); OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + const char *bind_interface = + config_get_string(main->Config(), "Output", "BindInterface"); + obs_data_set_string(output_settings, "bind_interface", bind_interface); const char *bind_ip = config_get_string(main->Config(), "Output", "BindIP"); obs_data_set_string(output_settings, "bind_ip", bind_ip); diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 6a09854197cfb..e6321f2561642 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -968,6 +968,8 @@ bool SimpleOutput::StartStreaming(obs_service_t *service) int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); + const char *bindInterface = + config_get_string(main->Config(), "Output", "BindInterface"); const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", @@ -978,6 +980,7 @@ bool SimpleOutput::StartStreaming(obs_service_t *service) config_get_bool(main->Config(), "Output", "DynamicBitrate"); OBSDataAutoRelease settings = obs_data_create(); + obs_data_set_string(settings, "bind_interface", bindInterface); obs_data_set_string(settings, "bind_ip", bindIP); obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); @@ -1867,6 +1870,8 @@ bool AdvancedOutput::StartStreaming(obs_service_t *service) int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); + const char *bindInterface = + config_get_string(main->Config(), "Output", "BindInterface"); const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", @@ -1877,6 +1882,7 @@ bool AdvancedOutput::StartStreaming(obs_service_t *service) config_get_bool(main->Config(), "Output", "DynamicBitrate"); OBSDataAutoRelease settings = obs_data_create(); + obs_data_set_string(settings, "bind_interface", bindInterface); obs_data_set_string(settings, "bind_ip", bindIP); obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 35fdc2134c76e..bb0e9bacfe339 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1473,6 +1473,8 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_uint(basicConfig, "Output", "RetryDelay", 2); config_set_default_uint(basicConfig, "Output", "MaxRetries", 25); + config_set_default_string(basicConfig, "Output", "BindInterface", + "default"); config_set_default_string(basicConfig, "Output", "BindIP", "default"); config_set_default_bool(basicConfig, "Output", "NewSocketLoopEnable", false); diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index ee9a4b3cbf9bf..cc5d06afdee7e 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -554,6 +554,9 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->reconnectMaxRetries, SCROLL_CHANGED, ADV_CHANGED); HookWidget(ui->processPriority, COMBO_CHANGED, ADV_CHANGED); HookWidget(ui->confirmOnExit, CHECK_CHANGED, ADV_CHANGED); +#ifdef __linux__ + HookWidget(ui->bindToInterface, COMBO_CHANGED, ADV_CHANGED); +#endif HookWidget(ui->bindToIP, COMBO_CHANGED, ADV_CHANGED); HookWidget(ui->enableNewSocketLoop, CHECK_CHANGED, ADV_CHANGED); HookWidget(ui->enableLowLatencyMode, CHECK_CHANGED, ADV_CHANGED); @@ -667,6 +670,13 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->resetOSXVSync = nullptr; #endif +#ifndef __linux__ + delete ui->bindToIfaceLabel; + delete ui->bindToInterface; + ui->bindToIfaceLabel = nullptr; + ui->bindToInterface = nullptr; +#endif + connect(ui->streamDelaySec, SIGNAL(valueChanged(int)), this, SLOT(UpdateStreamDelayEstimate())); connect(ui->outputMode, SIGNAL(currentIndexChanged(int)), this, @@ -816,9 +826,37 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) SLOT(AdvReplayBufferChanged())); connect(ui->advRBSecMax, SIGNAL(valueChanged(int)), this, SLOT(AdvReplayBufferChanged())); +#ifdef __linux__ + connect(ui->bindToInterface, SIGNAL(currentIndexChanged(int)), this, + SLOT(UpdateAddrList())); - // Get Bind to IP Addresses + ui->bindToIP->setEnabled(false); +#endif + + // Get Bind to interfaces Interfaces (Linux only) + // Get Bind to IP Addresses (Others) obs_properties_t *ppts = obs_get_output_properties("rtmp_output"); +#ifdef __linux__ + obs_property_t *p_iface = obs_properties_get(ppts, "bind_interface"); + QStringList dedup_iface = {}; + + ui->bindToInterface->blockSignals(true); + + size_t count_iface = obs_property_list_item_count(p_iface); + for (size_t i = 0; i < count_iface; i++) { + const char *name = obs_property_list_item_name(p_iface, i); + const char *val = obs_property_list_item_string(p_iface, i); + + // Add interfaces without duplicates + if (!dedup_iface.contains(QT_UTF8(name))) { + dedup_iface.append(QT_UTF8(name)); + ui->bindToInterface->addItem(QT_UTF8(name), val); + } + } + + UpdateAddrList(); + ui->bindToInterface->blockSignals(false); +#else obs_property_t *p = obs_properties_get(ppts, "bind_ip"); size_t count = obs_property_list_item_count(p); @@ -828,7 +866,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) ui->bindToIP->addItem(QT_UTF8(name), val); } - +#endif obs_properties_destroy(ppts); InitStreamPage(); @@ -2660,6 +2698,10 @@ void OBSBasicSettings::LoadAdvancedSettings() "FilenameFormatting"); bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); +#ifdef __linux__ + const char *bindInterface = + config_get_string(main->Config(), "Output", "BindInterface"); +#endif const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", @@ -2713,6 +2755,12 @@ void OBSBasicSettings::LoadAdvancedSettings() ui->sdrWhiteLevel->setValue(sdrWhiteLevel); ui->hdrNominalPeakLevel->setValue(hdrNominalPeakLevel); +#ifdef __linux__ + if (!SetComboByValue(ui->bindToInterface, bindInterface)) + SetInvalidValue(ui->bindToInterface, bindInterface, + bindInterface); +#endif + if (!SetComboByValue(ui->bindToIP, bindIP)) SetInvalidValue(ui->bindToIP, bindIP, bindIP); @@ -3439,6 +3487,9 @@ void OBSBasicSettings::SaveAdvancedSettings() SaveCheckBox(ui->reconnectEnable, "Output", "Reconnect"); SaveSpinBox(ui->reconnectRetryDelay, "Output", "RetryDelay"); SaveSpinBox(ui->reconnectMaxRetries, "Output", "MaxRetries"); +#ifdef __linux__ + SaveComboData(ui->bindToInterface, "Output", "BindInterface"); +#endif SaveComboData(ui->bindToIP, "Output", "BindIP"); SaveCheckBox(ui->autoRemux, "Video", "AutoRemux"); SaveCheckBox(ui->dynBitrate, "Output", "DynamicBitrate"); @@ -5594,5 +5645,41 @@ void OBSBasicSettings::UpdateAdvNetworkGroup() #ifdef _WIN32 ui->enableNewSocketLoop->setVisible(enabled); ui->enableLowLatencyMode->setVisible(enabled); +#elif defined(__linux__) + ui->bindToIfaceLabel->setVisible(enabled); + ui->bindToInterface->setVisible(enabled); #endif } + +#ifdef __linux__ +void OBSBasicSettings::UpdateAddrList() +{ + ui->bindToIP->clear(); + + // Get Bind to IP Addresses (Linux only) + obs_properties_t *ppts = obs_get_output_properties("rtmp_output"); + obs_property_t *p = obs_properties_get(ppts, "bind_ip"); + + size_t count_addr = obs_property_list_item_count(p); + for (size_t i = 0; i < count_addr; i++) { + const char *name = obs_property_list_item_name(p, i); + const char *val = obs_property_list_item_string(p, i); + + //Put only the addresses from the selected interface + if (QT_UTF8(name).contains( + ui->bindToInterface->currentText()) || + QT_UTF8(val).contains("default")) + ui->bindToIP->addItem(QT_UTF8(name), val); + } + + obs_properties_destroy(ppts); + + if (ui->bindToInterface->currentIndex() > 0) { + ui->bindToIP->setEnabled(true); + } else { + ui->bindToIP->setEnabled(false); + if (!SetComboByValue(ui->bindToIP, "default")) + SetInvalidValue(ui->bindToIP, "default", "default"); + } +} +#endif diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 80a6999cb3f09..e50cde4714c7f 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -461,6 +461,9 @@ private slots: void SetHotkeysIcon(const QIcon &icon); void SetAccessibilityIcon(const QIcon &icon); void SetAdvancedIcon(const QIcon &icon); +#ifdef __linux__ + void UpdateAddrList(); +#endif void UseStreamKeyAdvClicked();