tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1092a6e9f0d63b427114a606f81e05d3e8099ca5
parent a4d41f0bd6e06ac692d85fad90dd9db3d3489c32
Author: Michael Froman <mfroman@mozilla.com>
Date:   Wed,  8 Oct 2025 18:32:30 -0500

Bug 1993083 - Vendor libwebrtc from c575d44615

Upstream commit: https://webrtc.googlesource.com/src/+/c575d44615c2669468095089a55411815dca3855
    LNA: Check LNA permission when connection to STUN/TURN servers

    Plumbs LocalNetworkAccessPermissionFactory through BasicPortAllocator
    into UDPPort and TurnPort and checks the LNA permission before sending
    any messages to STUN/TURN servers.

    Blink side change: https://chromium-review.googlesource.com/c/chromium/src/+/6611819

    Bug: chromium:421223919
    Change-Id: I7c7e15013411f5270527a9e9caedc1a2d61e7564
    Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/394740
    Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
    Reviewed-by: Harald Alvestrand <hta@webrtc.org>
    Commit-Queue: Giovanni Ortuno Urquidi <ortuno@chromium.org>
    Cr-Commit-Position: refs/heads/main@{#45066}

Diffstat:
Mthird_party/libwebrtc/README.mozilla.last-vendor | 4++--
Mthird_party/libwebrtc/api/BUILD.gn | 8++++++--
Athird_party/libwebrtc/api/test/mock_local_network_access_permission.cc | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mthird_party/libwebrtc/api/test/mock_local_network_access_permission.h | 10++++++++++
Mthird_party/libwebrtc/moz-patch-stack/s0027.patch | 2+-
Mthird_party/libwebrtc/moz-patch-stack/s0103.patch | 2+-
Mthird_party/libwebrtc/p2p/BUILD.gn | 7+++++++
Mthird_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc | 30++----------------------------
Mthird_party/libwebrtc/p2p/base/port.cc | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mthird_party/libwebrtc/p2p/base/port.h | 22++++++++++++++++++++++
Mthird_party/libwebrtc/p2p/base/stun_port.cc | 19+++++++++++++++++--
Mthird_party/libwebrtc/p2p/base/stun_port_unittest.cc | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mthird_party/libwebrtc/p2p/base/turn_port.cc | 19+++++++++++++++++++
Mthird_party/libwebrtc/p2p/base/turn_port.h | 7+++++--
Mthird_party/libwebrtc/p2p/base/turn_port_unittest.cc | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mthird_party/libwebrtc/p2p/client/basic_port_allocator.cc | 22+++++++++++++++++-----
Mthird_party/libwebrtc/p2p/client/basic_port_allocator.h | 13++++++++++++-
Mthird_party/libwebrtc/p2p/client/relay_port_factory_interface.h | 3+++
18 files changed, 551 insertions(+), 51 deletions(-)

diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor @@ -1,4 +1,4 @@ # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc -libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-08T23:31:15.412531+00:00. +libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-08T23:32:19.144272+00:00. # base of lastest vendoring -643e2160c8 +c575d44615 diff --git a/third_party/libwebrtc/api/BUILD.gn b/third_party/libwebrtc/api/BUILD.gn @@ -1428,13 +1428,17 @@ if (rtc_include_tests) { ] } - rtc_source_set("mock_local_network_access_permission") { + rtc_library("mock_local_network_access_permission") { visibility = [ "*" ] testonly = true - sources = [ "test/mock_local_network_access_permission.h" ] + sources = [ + "test/mock_local_network_access_permission.cc", + "test/mock_local_network_access_permission.h", + ] deps = [ ":local_network_access_permission", "../rtc_base:socket_address", + "../rtc_base:threading", "../test:test_support", "//third_party/abseil-cpp/absl/functional:any_invocable", ] diff --git a/third_party/libwebrtc/api/test/mock_local_network_access_permission.cc b/third_party/libwebrtc/api/test/mock_local_network_access_permission.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2025 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "api/test/mock_local_network_access_permission.h" + +#include <memory> +#include <utility> + +#include "absl/functional/any_invocable.h" +#include "api/local_network_access_permission.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/thread.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using ::testing::_; + +namespace webrtc { + +FakeLocalNetworkAccessPermissionFactory:: + FakeLocalNetworkAccessPermissionFactory( + LocalNetworkAccessPermissionStatus status) { + EXPECT_CALL(*this, Create()).WillRepeatedly([status]() { + auto mock_lna_permission = + std::make_unique<MockLocalNetworkAccessPermission>(); + + EXPECT_CALL(*mock_lna_permission, RequestPermission(_, _)) + .WillRepeatedly( + [status]( + const SocketAddress& /* addr */, + absl::AnyInvocable<void( + webrtc::LocalNetworkAccessPermissionStatus)> callback) { + Thread::Current()->PostTask( + [callback = std::move(callback), status]() mutable { + callback(status); + }); + }); + + return mock_lna_permission; + }); +} + +FakeLocalNetworkAccessPermissionFactory:: + ~FakeLocalNetworkAccessPermissionFactory() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/api/test/mock_local_network_access_permission.h b/third_party/libwebrtc/api/test/mock_local_network_access_permission.h @@ -40,6 +40,16 @@ class MockLocalNetworkAccessPermissionFactory (override)); }; +// Class that returns LocalNetworkAccessPermission's that run their callback +// with the provided status. +class FakeLocalNetworkAccessPermissionFactory + : public MockLocalNetworkAccessPermissionFactory { + public: + explicit FakeLocalNetworkAccessPermissionFactory( + LocalNetworkAccessPermissionStatus status); + ~FakeLocalNetworkAccessPermissionFactory() override; +}; + } // namespace webrtc #endif // API_TEST_MOCK_LOCAL_NETWORK_ACCESS_PERMISSION_H_ diff --git a/third_party/libwebrtc/moz-patch-stack/s0027.patch b/third_party/libwebrtc/moz-patch-stack/s0027.patch @@ -203,7 +203,7 @@ index fd77e776cd..9d79e5e129 100644 } diff --git a/api/BUILD.gn b/api/BUILD.gn -index 706c48dcc6..f62fee9071 100644 +index 0b996dab6c..bb73b93245 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -44,6 +44,9 @@ rtc_library("enable_media") { diff --git a/third_party/libwebrtc/moz-patch-stack/s0103.patch b/third_party/libwebrtc/moz-patch-stack/s0103.patch @@ -126,7 +126,7 @@ index 74f7c78e12..f5aa3cc9fe 100644 "Generated during 'gn gen' by //BUILD.gn.", "", diff --git a/api/BUILD.gn b/api/BUILD.gn -index f62fee9071..d95dca73fd 100644 +index bb73b93245..1c1487a7ed 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn @@ -8,8 +8,8 @@ diff --git a/third_party/libwebrtc/p2p/BUILD.gn b/third_party/libwebrtc/p2p/BUILD.gn @@ -135,6 +135,7 @@ rtc_library("basic_port_allocator") { ":turn_port_factory", "../api:candidate", "../api:field_trials_view", + "../api:local_network_access_permission", "../api:packet_socket_factory", "../api:sequence_checker", "../api:turn_customizer", @@ -563,6 +564,7 @@ rtc_library("port") { "../api:array_view", "../api:candidate", "../api:field_trials_view", + "../api:local_network_access_permission", "../api:packet_socket_factory", "../api:rtc_error", "../api:sequence_checker", @@ -592,6 +594,8 @@ rtc_library("port") { "../rtc_base/network:sent_packet", "../rtc_base/system:rtc_export", "../rtc_base/third_party/sigslot", + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/functional:any_invocable", "//third_party/abseil-cpp/absl/memory", "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", @@ -759,6 +763,7 @@ rtc_library("stun_port") { "../api:async_dns_resolver", "../api:candidate", "../api:field_trials_view", + "../api:local_network_access_permission", "../api:packet_socket_factory", "../api/transport:stun_types", "../rtc_base:async_packet_socket", @@ -903,6 +908,7 @@ rtc_library("turn_port") { "../api:array_view", "../api:async_dns_resolver", "../api:candidate", + "../api:local_network_access_permission", "../api:packet_socket_factory", "../api:scoped_refptr", "../api:turn_customizer", @@ -954,6 +960,7 @@ rtc_source_set("relay_port_factory_interface") { deps = [ ":port", ":port_allocator", + "../api:local_network_access_permission", "../api:packet_socket_factory", "../api/environment", "../rtc_base:async_packet_socket", diff --git a/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc b/third_party/libwebrtc/p2p/base/p2p_transport_channel_unittest.cc @@ -283,32 +283,6 @@ class ResolverFactoryFixture : public webrtc::MockAsyncDnsResolverFactory { absl::AnyInvocable<void()> saved_callback_; }; -class PermissionFactoryFixture - : public webrtc::MockLocalNetworkAccessPermissionFactory { - public: - explicit PermissionFactoryFixture( - webrtc::LocalNetworkAccessPermissionStatus result) { - EXPECT_CALL(*this, Create()).WillRepeatedly([result]() { - auto mock_lna_permission = - std::make_unique<webrtc::MockLocalNetworkAccessPermission>(); - - EXPECT_CALL(*mock_lna_permission, RequestPermission(_, _)) - .WillRepeatedly( - [result]( - const SocketAddress& /* addr */, - absl::AnyInvocable<void( - webrtc::LocalNetworkAccessPermissionStatus)> callback) { - webrtc::Thread::Current()->PostTask( - [callback = std::move(callback), result]() mutable { - callback(result); - }); - }); - - return mock_lna_permission; - }); - } -}; - bool HasLocalAddress(const webrtc::CandidatePairInterface* pair, const SocketAddress& address) { return pair->local_candidate().address().EqualIPs(address); @@ -7263,7 +7237,7 @@ class LocalAreaNetworkPermissionTest TEST_P(LocalAreaNetworkPermissionTest, LiteralAddresses) { const Environment env = CreateEnvironment(); FakePortAllocator pa(env, ss()); - PermissionFactoryFixture lna_permission_factory( + webrtc::FakeLocalNetworkAccessPermissionFactory lna_permission_factory( GetParam().lna_permission_status); IceTransportInit init; @@ -7292,7 +7266,7 @@ TEST_P(LocalAreaNetworkPermissionTest, LiteralAddresses) { TEST_P(LocalAreaNetworkPermissionTest, UnresolvedAddresses) { const Environment env = CreateEnvironment(); FakePortAllocator pa(env, ss()); - PermissionFactoryFixture lna_permission_factory( + webrtc::FakeLocalNetworkAccessPermissionFactory lna_permission_factory( GetParam().lna_permission_status); ResolverFactoryFixture resolver_fixture; diff --git a/third_party/libwebrtc/p2p/base/port.cc b/third_party/libwebrtc/p2p/base/port.cc @@ -19,11 +19,14 @@ #include <utility> #include <vector> +#include "absl/algorithm/container.h" +#include "absl/functional/any_invocable.h" #include "absl/memory/memory.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "api/array_view.h" #include "api/candidate.h" +#include "api/local_network_access_permission.h" #include "api/sequence_checker.h" #include "api/task_queue/task_queue_base.h" #include "api/transport/stun.h" @@ -113,6 +116,7 @@ Port::Port(const PortParametersRef& args, : env_(args.env), thread_(args.network_thread), factory_(args.socket_factory), + lna_permission_factory_(args.lna_permission_factory), type_(type), send_retransmit_count_attribute_(false), network_(args.network), @@ -938,4 +942,50 @@ void Port::CopyPortInformationToPacketInfo(PacketInfo* info) const { info->network_id = Network()->id(); } +void Port::MaybeRequestLocalNetworkAccessPermission( + const SocketAddress& address, + absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback) { + if (!lna_permission_factory_) { + std::move(callback)(LocalNetworkAccessPermissionStatus::kGranted); + return; + } + + if (!address.IsPrivateIP() && !address.IsLoopbackIP()) { + std::move(callback)(LocalNetworkAccessPermissionStatus::kGranted); + return; + } + + RTC_LOG(LS_VERBOSE) << "Asynchronously requesting LNA permission." + << address.HostAsSensitiveURIString(); + + std::unique_ptr<LocalNetworkAccessPermissionInterface> permission_query = + lna_permission_factory_->Create(); + auto* permission_query_ptr = permission_query.get(); + permission_queries_.push_back(std::move(permission_query)); + + permission_query_ptr->RequestPermission( + address, [this, permission_query_ptr, callback = std::move(callback)]( + LocalNetworkAccessPermissionStatus status) mutable { + OnRequestLocalNetworkAccessPermission(permission_query_ptr, + std::move(callback), status); + }); +} + +void Port::OnRequestLocalNetworkAccessPermission( + LocalNetworkAccessPermissionInterface* permission_query, + absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback, + LocalNetworkAccessPermissionStatus status) { + auto it = + absl::c_find_if(permission_queries_, [permission_query](const auto& q) { + return q.get() == permission_query; + }); + if (it == permission_queries_.end()) { + RTC_LOG(LS_ERROR) << "Unexpected LocalNetworkAccessPermission return"; + RTC_DCHECK_NOTREACHED(); + } + + permission_queries_.erase(it); + std::move(callback)(status); +} + } // namespace webrtc diff --git a/third_party/libwebrtc/p2p/base/port.h b/third_party/libwebrtc/p2p/base/port.h @@ -22,10 +22,12 @@ #include <utility> #include <vector> +#include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "api/candidate.h" #include "api/environment/environment.h" #include "api/field_trials_view.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/sequence_checker.h" #include "api/task_queue/task_queue_base.h" @@ -167,6 +169,8 @@ class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> { const ::webrtc::Network* network; absl::string_view ice_username_fragment; absl::string_view ice_password; + LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory = + nullptr; }; protected: @@ -431,6 +435,15 @@ class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> { IceCandidateType type() const { return type_; } + // Requests the Local Network Access Permission if necessary. Asynchronously + // calls `callback` with the result of requesting the permission. If the + // permission is not needed e.g. because `address` is public, it calls + // `callback` synchronously. It's guaranteed that the callback won't be called + // after this class is destroyed. + void MaybeRequestLocalNetworkAccessPermission( + const SocketAddress& address, + absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback); + private: bool MaybeObfuscateAddress(const Candidate& c, bool is_final) RTC_RUN_ON(thread_); @@ -453,9 +466,15 @@ class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> { void OnNetworkTypeChanged(const ::webrtc::Network* network); + void OnRequestLocalNetworkAccessPermission( + LocalNetworkAccessPermissionInterface* permission_query, + absl::AnyInvocable<void(LocalNetworkAccessPermissionStatus)> callback, + LocalNetworkAccessPermissionStatus status); + const Environment env_; TaskQueueBase* const thread_; PacketSocketFactory* const factory_; + LocalNetworkAccessPermissionFactoryInterface* const lna_permission_factory_; const IceCandidateType type_; bool send_retransmit_count_attribute_; const ::webrtc::Network* network_; @@ -493,6 +512,9 @@ class RTC_EXPORT Port : public PortInterface, public sigslot::has_slots<> { MdnsNameRegistrationStatus mdns_name_registration_status_ = MdnsNameRegistrationStatus::kNotStarted; + std::vector<std::unique_ptr<LocalNetworkAccessPermissionInterface>> + permission_queries_; + CallbackList<PortInterface*> port_destroyed_callback_list_; // Keep as the last member variable. diff --git a/third_party/libwebrtc/p2p/base/stun_port.cc b/third_party/libwebrtc/p2p/base/stun_port.cc @@ -23,6 +23,7 @@ #include "api/async_dns_resolver.h" #include "api/candidate.h" #include "api/field_trials_view.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/transport/stun.h" #include "p2p/base/connection.h" @@ -479,7 +480,7 @@ void UDPPort::SendStunBindingRequest(const SocketAddress& stun_addr) { return; } - // Check if `server_addr_` is compatible with the port's ip. + // Check if `server_addr_` is compatible with the port's ip. if (!IsCompatibleAddress(stun_addr)) { // Since we can't send stun messages to the server, we should mark this // port ready. This is not an error but similar to ignoring @@ -495,7 +496,21 @@ void UDPPort::SendStunBindingRequest(const SocketAddress& stun_addr) { static_cast<int>(stun_addr.GetIPAddressType()), static_cast<int>(IPAddressType::kMaxValue)); - request_manager_.Send(new StunBindingRequest(this, stun_addr, TimeMillis())); + MaybeRequestLocalNetworkAccessPermission( + stun_addr, [this, stun_addr](LocalNetworkAccessPermissionStatus status) { + if (status != LocalNetworkAccessPermissionStatus::kGranted) { + RTC_LOG(LS_WARNING) + << ToString() << ": Permission denied to connect to STUN server " + << stun_addr.HostAsSensitiveURIString(); + OnStunBindingOrResolveRequestFailed( + stun_addr, STUN_ERROR_NOT_AN_ERROR, + "Not allowed to connecto to STUN server."); + return; + } + + request_manager_.Send( + new StunBindingRequest(this, stun_addr, TimeMillis())); + }); } bool UDPPort::MaybeSetDefaultLocalAddress(SocketAddress* addr) const { diff --git a/third_party/libwebrtc/p2p/base/stun_port_unittest.cc b/third_party/libwebrtc/p2p/base/stun_port_unittest.cc @@ -16,6 +16,7 @@ #include <optional> #include <set> #include <string> +#include <utility> #include <vector> #include "absl/functional/any_invocable.h" @@ -24,8 +25,10 @@ #include "api/environment/environment_factory.h" #include "api/field_trials.h" #include "api/field_trials_view.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/test/mock_async_dns_resolver.h" +#include "api/test/mock_local_network_access_permission.h" #include "api/test/rtc_error_matchers.h" #include "api/transport/stun.h" #include "api/units/time_delta.h" @@ -191,21 +194,26 @@ class StunPortTestBase : public ::testing::Test, public sigslot::has_slots<> { } void CreateStunPort(const webrtc::SocketAddress& server_addr, - const webrtc::FieldTrialsView* field_trials = nullptr) { + const webrtc::FieldTrialsView* field_trials = nullptr, + webrtc::LocalNetworkAccessPermissionFactoryInterface* + lna_permission_factory = nullptr) { ServerAddresses stun_servers; stun_servers.insert(server_addr); - CreateStunPort(stun_servers, field_trials); + CreateStunPort(stun_servers, field_trials, lna_permission_factory); } void CreateStunPort(const ServerAddresses& stun_servers, - const webrtc::FieldTrialsView* field_trials = nullptr) { + const webrtc::FieldTrialsView* field_trials = nullptr, + webrtc::LocalNetworkAccessPermissionFactoryInterface* + lna_permission_factory = nullptr) { stun_port_ = webrtc::StunPort::Create( {.env = CreateEnvironment(field_trials), .network_thread = &thread_, .socket_factory = socket_factory(), .network = network_, .ice_username_fragment = webrtc::CreateRandomString(16), - .ice_password = webrtc::CreateRandomString(22)}, + .ice_password = webrtc::CreateRandomString(22), + .lna_permission_factory = lna_permission_factory}, 0, 0, stun_servers, std::nullopt); stun_port_->SetIceTiebreaker(kTiebreakerDefault); stun_port_->set_stun_keepalive_delay(stun_keepalive_delay_); @@ -772,6 +780,161 @@ INSTANTIATE_TEST_SUITE_P(All, StunPortIPAddressTypeMetricsTest, ::testing::ValuesIn(kAllIPAddressTypeTestConfigs)); +struct LocalAreaNetworkPermissionTestConfig { + template <typename Sink> + friend void AbslStringify( + Sink& sink, + const LocalAreaNetworkPermissionTestConfig& config) { + sink.Append(config.address); + sink.Append("_"); + switch (config.lna_permission_status) { + case webrtc::LocalNetworkAccessPermissionStatus::kDenied: + sink.Append("Denied"); + break; + case webrtc::LocalNetworkAccessPermissionStatus::kGranted: + sink.Append("Granted"); + break; + } + } + + webrtc::LocalNetworkAccessPermissionStatus lna_permission_status; + absl::string_view address; + bool should_succeed; +} kAllLocalAreNetworkPermissionTestConfigs[] = { + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "127.0.0.1", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "10.0.0.3", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "1.1.1.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "::1", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, + "fd00:4860:4860::8844", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, + "2001:4860:4860::8888", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "127.0.0.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "10.0.0.3", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "1.1.1.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "::1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, + "fd00:4860:4860::8844", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, + "2001:4860:4860::8888", + /*should_succeed=*/true}, +}; + +class StunPortLocalNetworkAccessPermissionTest + : public FakeClockBase, + public StunPortTestBase, + public ::testing::WithParamInterface< + LocalAreaNetworkPermissionTestConfig> { + public: + StunPortLocalNetworkAccessPermissionTest() + : StunPortTestBase( + /*address=*/using_ipv6_address() ? kPrivateIPv6.ipaddr() + : kPrivateIP.ipaddr(), + /*stun_server_addresses=*/{{GetParam().address, 5000}}, + /*nat_server_address=*/ + using_ipv6_address() ? kNatAddrIPv6 : kNatAddr), + socket_factory_(ss()) {} + + protected: + webrtc::PacketSocketFactory* socket_factory() override { + return &socket_factory_; + } + + void setup_dns_resolver_mock() { + auto expectations = + [](webrtc::MockAsyncDnsResolver* resolver, + webrtc::MockAsyncDnsResolverResult* resolver_result) { + EXPECT_CALL(*resolver, Start(_, _, _)) + .WillOnce( + [](const webrtc::SocketAddress& /* addr */, int /* family */, + absl::AnyInvocable<void()> callback) { callback(); }); + + EXPECT_CALL(*resolver, result) + .WillRepeatedly(ReturnPointee(resolver_result)); + EXPECT_CALL(*resolver_result, GetError).WillOnce(Return(0)); + EXPECT_CALL(*resolver_result, GetResolvedAddress(_, _)) + .WillOnce(DoAll( + SetArgPointee<1>(SocketAddress(GetParam().address, 5000)), + Return(true))); + }; + + socket_factory_.SetExpectations(std::move(expectations)); + } + + private: + bool using_ipv6_address() { + return SocketAddress(GetParam().address, 5000).family() == AF_INET6; + } + + webrtc::MockDnsResolvingPacketSocketFactory socket_factory_; +}; + +TEST_P(StunPortLocalNetworkAccessPermissionTest, ResolvedAddresses) { + webrtc::FakeLocalNetworkAccessPermissionFactory factory( + GetParam().lna_permission_status); + + CreateStunPort({GetParam().address, 5000}, /*field_trials=*/nullptr, + &factory); + + PrepareAddress(); + EXPECT_THAT( + webrtc::WaitUntil([&] { return done(); }, IsTrue(), + {.timeout = webrtc::TimeDelta::Millis(kTimeoutMs), + .clock = &fake_clock}), + webrtc::IsRtcOk()); + + if (GetParam().should_succeed) { + EXPECT_FALSE(error()); + EXPECT_EQ(1U, port()->Candidates().size()); + EXPECT_EQ(0, error_event_.error_code); + } else { + EXPECT_TRUE(error()); + EXPECT_EQ(0U, port()->Candidates().size()); + EXPECT_EQ(0, error_event_.error_code); + } +} + +TEST_P(StunPortLocalNetworkAccessPermissionTest, UnresolvedAddresses) { + setup_dns_resolver_mock(); + + webrtc::FakeLocalNetworkAccessPermissionFactory factory( + GetParam().lna_permission_status); + CreateStunPort({"fakehost.test", 5000}, /*field_trials=*/nullptr, &factory); + + PrepareAddress(); + EXPECT_THAT( + webrtc::WaitUntil([&] { return done(); }, IsTrue(), + {.timeout = webrtc::TimeDelta::Millis(kTimeoutMs), + .clock = &fake_clock}), + webrtc::IsRtcOk()); + + if (GetParam().should_succeed) { + EXPECT_FALSE(error()); + EXPECT_EQ(1U, port()->Candidates().size()); + EXPECT_EQ(0, error_event_.error_code); + } else { + EXPECT_TRUE(error()); + EXPECT_EQ(0U, port()->Candidates().size()); + EXPECT_EQ(0, error_event_.error_code); + } +} + +INSTANTIATE_TEST_SUITE_P( + All, + StunPortLocalNetworkAccessPermissionTest, + ::testing::ValuesIn(kAllLocalAreNetworkPermissionTestConfigs)); + class MockAsyncPacketSocket : public webrtc::AsyncPacketSocket { public: ~MockAsyncPacketSocket() override = default; diff --git a/third_party/libwebrtc/p2p/base/turn_port.cc b/third_party/libwebrtc/p2p/base/turn_port.cc @@ -25,6 +25,7 @@ #include "absl/strings/string_view.h" #include "api/array_view.h" #include "api/candidate.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/scoped_refptr.h" #include "api/task_queue/pending_task_safety_flag.h" @@ -392,6 +393,24 @@ void TurnPort::PrepareAddress() { static_cast<int>(server_address_.address.GetIPAddressType()), static_cast<int>(IPAddressType::kMaxValue)); + MaybeRequestLocalNetworkAccessPermission( + server_address_.address, + [this](LocalNetworkAccessPermissionStatus status) { + if (status != LocalNetworkAccessPermissionStatus::kGranted) { + RTC_LOG(LS_ERROR) + << ToString() << ": Permission denied to connect to TURN server " + << server_address_.address.HostAsSensitiveURIString(); + OnAllocateError(STUN_ERROR_NOT_AN_ERROR, + "Attempt to start allocation without Local Network " + "Access Permission."); + return; + } + + OnLocalNetworkAccessPermissionGranted(); + }); +} + +void TurnPort::OnLocalNetworkAccessPermissionGranted() { // Insert the current address to prevent redirection pingpong. attempted_server_addresses_.insert(server_address_.address); diff --git a/third_party/libwebrtc/p2p/base/turn_port.h b/third_party/libwebrtc/p2p/base/turn_port.h @@ -92,7 +92,8 @@ class TurnPort : public Port { .socket_factory = args.socket_factory, .network = args.network, .ice_username_fragment = args.username, - .ice_password = args.password}, + .ice_password = args.password, + .lna_permission_factory = args.lna_permission_factory}, socket, *args.server_address, args.config->credentials, args.relative_priority, args.config->tls_alpn_protocols, args.config->tls_elliptic_curves, args.turn_customizer, @@ -114,7 +115,8 @@ class TurnPort : public Port { .socket_factory = args.socket_factory, .network = args.network, .ice_username_fragment = args.username, - .ice_password = args.password}, + .ice_password = args.password, + .lna_permission_factory = args.lna_permission_factory}, min_port, max_port, *args.server_address, args.config->credentials, args.relative_priority, args.config->tls_alpn_protocols, args.config->tls_elliptic_curves, args.turn_customizer, @@ -258,6 +260,7 @@ class TurnPort : public Port { bool SetAlternateServer(const SocketAddress& address); void ResolveTurnAddress(const SocketAddress& address); void OnResolveResult(const AsyncDnsResolverResult& result); + void OnLocalNetworkAccessPermissionGranted(); void AddRequestAuthInfo(StunMessage* msg); void OnSendStunPacket(const void* data, size_t size, StunRequest* request); diff --git a/third_party/libwebrtc/p2p/base/turn_port_unittest.cc b/third_party/libwebrtc/p2p/base/turn_port_unittest.cc @@ -24,8 +24,10 @@ #include "api/candidate.h" #include "api/environment/environment.h" #include "api/environment/environment_factory.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/test/mock_async_dns_resolver.h" +#include "api/test/mock_local_network_access_permission.h" #include "api/test/rtc_error_matchers.h" #include "api/transport/stun.h" #include "api/units/time_delta.h" @@ -279,9 +281,12 @@ class TurnPortTest : public ::testing::Test, bool CreateTurnPort(const SocketAddress& local_address, absl::string_view username, absl::string_view password, - const ProtocolAddress& server_address) { + const ProtocolAddress& server_address, + LocalNetworkAccessPermissionFactoryInterface* + lna_permission_factory = nullptr) { return CreateTurnPortWithAllParams(MakeNetwork(local_address), username, - password, server_address); + password, server_address, + lna_permission_factory); } bool CreateTurnPortWithNetwork(const Network* network, @@ -298,7 +303,9 @@ class TurnPortTest : public ::testing::Test, bool CreateTurnPortWithAllParams(const Network* network, absl::string_view username, absl::string_view password, - const ProtocolAddress& server_address) { + const ProtocolAddress& server_address, + LocalNetworkAccessPermissionFactoryInterface* + lna_permission_factory = nullptr) { RelayServerConfig config; config.credentials = RelayCredentials(username, password); CreateRelayPortArgs args = {.env = env_}; @@ -310,6 +317,7 @@ class TurnPortTest : public ::testing::Test, args.server_address = &server_address; args.config = &config; args.turn_customizer = turn_customizer_.get(); + args.lna_permission_factory = lna_permission_factory; turn_port_ = TurnPort::Create(args, 0, 0); if (!turn_port_) { @@ -2237,4 +2245,150 @@ INSTANTIATE_TEST_SUITE_P(All, TurnPortIPAddressTypeMetricsTest, ::testing::ValuesIn(kAllIPAddressTypeTestConfigs)); +struct LocalAreaNetworkPermissionTestConfig { + template <typename Sink> + friend void AbslStringify( + Sink& sink, + const LocalAreaNetworkPermissionTestConfig& config) { + sink.Append(config.address); + sink.Append("_"); + switch (config.lna_permission_status) { + case webrtc::LocalNetworkAccessPermissionStatus::kDenied: + sink.Append("Denied"); + break; + case webrtc::LocalNetworkAccessPermissionStatus::kGranted: + sink.Append("Granted"); + break; + } + } + + webrtc::LocalNetworkAccessPermissionStatus lna_permission_status; + absl::string_view address; + bool should_succeed; +} kAllLocalAreNetworkPermissionTestConfigs[] = { + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "127.0.0.1", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "10.0.0.3", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "1.1.1.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, "::1", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, + "fd00:4860:4860::8844", + /*should_succeed=*/false}, + {webrtc::LocalNetworkAccessPermissionStatus::kDenied, + "2001:4860:4860::8888", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "127.0.0.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "10.0.0.3", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "1.1.1.1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, "::1", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, + "fd00:4860:4860::8844", + /*should_succeed=*/true}, + {webrtc::LocalNetworkAccessPermissionStatus::kGranted, + "2001:4860:4860::8888", + /*should_succeed=*/true}, +}; + +class TurnPortLocalNetworkAccessPermissionTest + : public TurnPortWithMockDnsResolverTest, + public ::testing::WithParamInterface< + LocalAreaNetworkPermissionTestConfig> { + protected: + void CreateTurnPort( + absl::string_view address, + LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory) { + ProtocolAddress server_address({address, 5000}, PROTO_UDP); + + // Use the test's address instead of `server_address` because + // `server_address` might not be a resolved address with an unknown family. + SocketAddress local_address = + SocketAddress(GetParam().address, 5000).family() == AF_INET6 + ? kLocalIPv6Addr + : kLocalAddr1; + TurnPortWithMockDnsResolverTest::CreateTurnPort( + local_address, kTurnUsername, kTurnPassword, server_address, + lna_permission_factory); + } + + void setup_dns_resolver_mock() { + auto expectations = + [](webrtc::MockAsyncDnsResolver* resolver, + webrtc::MockAsyncDnsResolverResult* resolver_result) { + EXPECT_CALL(*resolver, Start(_, _, _)) + .WillOnce( + [](const webrtc::SocketAddress& /* addr */, int /* family */, + absl::AnyInvocable<void()> callback) { callback(); }); + + EXPECT_CALL(*resolver, result) + .WillRepeatedly(ReturnPointee(resolver_result)); + EXPECT_CALL(*resolver_result, GetError).WillRepeatedly(Return(0)); + EXPECT_CALL(*resolver_result, GetResolvedAddress(_, _)) + .WillOnce(DoAll( + SetArgPointee<1>(SocketAddress(GetParam().address, 5000)), + Return(true))); + }; + + SetDnsResolverExpectations(std::move(expectations)); + } +}; + +TEST_P(TurnPortLocalNetworkAccessPermissionTest, ResolvedAddresses) { + turn_server_.AddInternalSocket({GetParam().address, 5000}, PROTO_UDP); + + FakeLocalNetworkAccessPermissionFactory factory( + GetParam().lna_permission_status); + CreateTurnPort(GetParam().address, &factory); + turn_port_->PrepareAddress(); + + if (GetParam().should_succeed) { + EXPECT_THAT(WaitUntil([&] { return turn_ready_; }, IsTrue(), + {.clock = &fake_clock_}), + IsRtcOk()); + EXPECT_EQ(1u, turn_port_->Candidates().size()); + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); + } else { + EXPECT_THAT(WaitUntil([&] { return turn_error_; }, IsTrue(), + {.clock = &fake_clock_}), + IsRtcOk()); + EXPECT_EQ(0u, turn_port_->Candidates().size()); + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); + } +} + +TEST_P(TurnPortLocalNetworkAccessPermissionTest, UnresolvedAddresses) { + turn_server_.AddInternalSocket({GetParam().address, 5000}, PROTO_UDP); + setup_dns_resolver_mock(); + + FakeLocalNetworkAccessPermissionFactory factory( + GetParam().lna_permission_status); + CreateTurnPort("fakehost.test", &factory); + turn_port_->PrepareAddress(); + + if (GetParam().should_succeed) { + EXPECT_THAT(WaitUntil([&] { return turn_ready_; }, IsTrue(), + {.clock = &fake_clock_}), + IsRtcOk()); + EXPECT_EQ(1u, turn_port_->Candidates().size()); + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); + } else { + EXPECT_THAT(WaitUntil([&] { return turn_error_; }, IsTrue(), + {.clock = &fake_clock_}), + IsRtcOk()); + EXPECT_EQ(0u, turn_port_->Candidates().size()); + EXPECT_NE(SOCKET_ERROR, turn_port_->error()); + } +} + +INSTANTIATE_TEST_SUITE_P( + All, + TurnPortLocalNetworkAccessPermissionTest, + ::testing::ValuesIn(kAllLocalAreNetworkPermissionTestConfigs)); + } // namespace webrtc diff --git a/third_party/libwebrtc/p2p/client/basic_port_allocator.cc b/third_party/libwebrtc/p2p/client/basic_port_allocator.cc @@ -28,6 +28,7 @@ #include "api/candidate.h" #include "api/environment/environment.h" #include "api/field_trials_view.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/sequence_checker.h" #include "api/task_queue/pending_task_safety_flag.h" @@ -181,11 +182,14 @@ BasicPortAllocator::BasicPortAllocator( NetworkManager* absl_nonnull network_manager, PacketSocketFactory* absl_nonnull socket_factory, TurnCustomizer* absl_nullable turn_customizer, - RelayPortFactoryInterface* absl_nullable relay_port_factory) + RelayPortFactoryInterface* absl_nullable relay_port_factory, + std::unique_ptr<LocalNetworkAccessPermissionFactoryInterface> absl_nullable + lna_permission_factory) : env_(env), network_manager_(network_manager), socket_factory_(socket_factory), - relay_port_factory_(relay_port_factory) { + relay_port_factory_(relay_port_factory), + lna_permission_factory_(std::move(lna_permission_factory)) { RTC_CHECK(socket_factory_); RTC_DCHECK(network_manager_); SetConfiguration(ServerAddresses(), std::vector<RelayServerConfig>(), 0, @@ -1456,7 +1460,9 @@ void AllocationSequence::CreateUDPPorts() { .socket_factory = session_->socket_factory(), .network = network_, .ice_username_fragment = session_->username(), - .ice_password = session_->password()}, + .ice_password = session_->password(), + .lna_permission_factory = + session_->allocator()->lna_permission_factory()}, udp_socket_.get(), emit_local_candidate_for_anyaddress, session_->allocator()->stun_candidate_keepalive_interval()); } else { @@ -1466,7 +1472,9 @@ void AllocationSequence::CreateUDPPorts() { .socket_factory = session_->socket_factory(), .network = network_, .ice_username_fragment = session_->username(), - .ice_password = session_->password()}, + .ice_password = session_->password(), + .lna_permission_factory = + session_->allocator()->lna_permission_factory()}, session_->allocator()->min_port(), session_->allocator()->max_port(), emit_local_candidate_for_anyaddress, session_->allocator()->stun_candidate_keepalive_interval()); @@ -1541,7 +1549,9 @@ void AllocationSequence::CreateStunPorts() { .socket_factory = session_->socket_factory(), .network = network_, .ice_username_fragment = session_->username(), - .ice_password = session_->password()}, + .ice_password = session_->password(), + .lna_permission_factory = + session_->allocator()->lna_permission_factory()}, session_->allocator()->min_port(), session_->allocator()->max_port(), config_->StunServers(), session_->allocator()->stun_candidate_keepalive_interval()); @@ -1613,6 +1623,8 @@ void AllocationSequence::CreateTurnPort(const RelayServerConfig& config, args.config = &config; args.turn_customizer = session_->allocator()->turn_customizer(); args.relative_priority = relative_priority; + args.lna_permission_factory = + session_->allocator()->lna_permission_factory(); std::unique_ptr<Port> port; // Shared socket mode must be enabled only for UDP based ports. Hence diff --git a/third_party/libwebrtc/p2p/client/basic_port_allocator.h b/third_party/libwebrtc/p2p/client/basic_port_allocator.h @@ -24,6 +24,7 @@ #include "api/candidate.h" #include "api/environment/environment.h" #include "api/field_trials_view.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "api/task_queue/pending_task_safety_flag.h" #include "api/transport/enums.h" @@ -55,7 +56,9 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator { NetworkManager* absl_nonnull network_manager, PacketSocketFactory* absl_nonnull socket_factory, TurnCustomizer* absl_nullable turn_customizer = nullptr, - RelayPortFactoryInterface* absl_nullable relay_port_factory = nullptr); + RelayPortFactoryInterface* absl_nullable relay_port_factory = nullptr, + std::unique_ptr<LocalNetworkAccessPermissionFactoryInterface> + absl_nullable lna_permission_factory = nullptr); BasicPortAllocator(const BasicPortAllocator&) = delete; BasicPortAllocator& operator=(const BasicPortAllocator&) = delete; @@ -78,6 +81,11 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator { return socket_factory_; } + LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory() { + CheckRunOnValidThreadIfInitialized(); + return lna_permission_factory_.get(); + } + PortAllocatorSession* CreateSessionInternal( absl::string_view content_name, int component, @@ -107,6 +115,9 @@ class RTC_EXPORT BasicPortAllocator : public PortAllocator { AlwaysValidPointer<RelayPortFactoryInterface, TurnPortFactory> relay_port_factory_; + + std::unique_ptr<LocalNetworkAccessPermissionFactoryInterface> + lna_permission_factory_; }; struct PortConfiguration; diff --git a/third_party/libwebrtc/p2p/client/relay_port_factory_interface.h b/third_party/libwebrtc/p2p/client/relay_port_factory_interface.h @@ -15,6 +15,7 @@ #include <string> #include "api/environment/environment.h" +#include "api/local_network_access_permission.h" #include "api/packet_socket_factory.h" #include "p2p/base/port.h" #include "p2p/base/port_allocator.h" @@ -44,6 +45,8 @@ struct CreateRelayPortArgs { // to the candidates from other servers. Required because ICE priorities // need to be unique. int relative_priority = 0; + LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory = + nullptr; }; // A factory for creating RelayPort's.