/* * Copyright (C) 2020-2021 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "NetworkRTCTCPSocketCocoa.h" #if USE(LIBWEBRTC) && PLATFORM(COCOA) #include "DataReference.h" #include "LibWebRTCNetworkMessages.h" #include "Logging.h" #include "NWSPI.h" #include "NetworkRTCUtilitiesCocoa.h" #include #include #include namespace WebKit { using namespace WebCore; static dispatch_queue_t tcpSocketQueue() { static dispatch_queue_t queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ queue = dispatch_queue_create("WebRTC TCP socket queue", DISPATCH_QUEUE_CONCURRENT); }); return queue; } std::unique_ptr NetworkRTCTCPSocketCocoa::createClientTCPSocket(LibWebRTCSocketIdentifier identifier, NetworkRTCProvider& rtcProvider, const rtc::SocketAddress& remoteAddress, int tcpOptions, const String& attributedBundleIdentifier, bool isFirstParty, bool isRelayDisabled, const WebCore::RegistrableDomain& domain, Ref&& connection) { // FIXME: We should support ssltcp candidates, maybe support OPT_TLS_INSECURE as well. return makeUnique(identifier, rtcProvider, remoteAddress, tcpOptions, attributedBundleIdentifier, isFirstParty, isRelayDisabled, domain, WTFMove(connection)); } static inline void processIncomingData(RetainPtr&& nwConnection, Function(Vector&&)>&& processData, Vector&& buffer = { }) { auto nwConnectionReference = nwConnection.get(); nw_connection_receive(nwConnectionReference, 1, std::numeric_limits::max(), makeBlockPtr([nwConnection = WTFMove(nwConnection), processData = WTFMove(processData), buffer = WTFMove(buffer)](dispatch_data_t content, nw_content_context_t context, bool isComplete, nw_error_t error) mutable { if (content) { dispatch_data_apply(content, makeBlockPtr([&](dispatch_data_t, size_t, const void* data, size_t size) { // FIXME: Introduce uncheckedAppend version. buffer.append(static_cast(data), size); return true; }).get()); buffer = processData(WTFMove(buffer)); } if (isComplete && context && nw_content_context_get_is_final(context)) return; if (error) { RELEASE_LOG_ERROR(WebRTC, "NetworkRTCTCPSocketCocoa processIncomingData failed with error %d", nw_error_get_error_code(error)); return; } processIncomingData(WTFMove(nwConnection), WTFMove(processData), WTFMove(buffer)); }).get()); } NetworkRTCTCPSocketCocoa::NetworkRTCTCPSocketCocoa(LibWebRTCSocketIdentifier identifier, NetworkRTCProvider& rtcProvider, const rtc::SocketAddress& remoteAddress, int options, const String& attributedBundleIdentifier, bool isFirstParty, bool isRelayDisabled, const WebCore::RegistrableDomain& domain, Ref&& connection) : m_identifier(identifier) , m_rtcProvider(rtcProvider) , m_connection(WTFMove(connection)) , m_isSTUN(options & rtc::PacketSocketFactory::OPT_STUN) { auto hostName = remoteAddress.hostname(); if (hostName.empty()) hostName = remoteAddress.ipaddr().ToString(); auto host = adoptNS(nw_endpoint_create_host(hostName.c_str(), String::number(remoteAddress.port()).utf8().data())); // FIXME: Handle TLS certificate validation like for other network code paths, using sec_protocol_options_set_verify_block bool isTLS = options & rtc::PacketSocketFactory::OPT_TLS; auto tcpTLS = adoptNS(nw_parameters_create_secure_tcp(isTLS ? NW_PARAMETERS_DEFAULT_CONFIGURATION : NW_PARAMETERS_DISABLE_PROTOCOL, ^(nw_protocol_options_t tcp_options) { nw_tcp_options_set_no_delay(tcp_options, true); })); setNWParametersApplicationIdentifiers(tcpTLS.get(), rtcProvider.applicationBundleIdentifier(), rtcProvider.sourceApplicationAuditToken(), attributedBundleIdentifier); setNWParametersTrackerOptions(tcpTLS.get(), isRelayDisabled, isFirstParty, isKnownTracker(domain)); m_nwConnection = adoptNS(nw_connection_create(host.get(), tcpTLS.get())); nw_connection_set_queue(m_nwConnection.get(), tcpSocketQueue()); nw_connection_set_state_changed_handler(m_nwConnection.get(), makeBlockPtr([identifier = m_identifier, rtcProvider = Ref { rtcProvider }, connection = m_connection.copyRef()](nw_connection_state_t state, _Nullable nw_error_t error) { ASSERT_UNUSED(error, !error); switch (state) { case nw_connection_state_invalid: case nw_connection_state_waiting: case nw_connection_state_preparing: return; case nw_connection_state_ready: connection->send(Messages::LibWebRTCNetwork::SignalConnect(identifier), 0); return; case nw_connection_state_failed: rtcProvider->callOnRTCNetworkThread([rtcProvider, identifier] { rtcProvider->closeSocket(identifier); }); connection->send(Messages::LibWebRTCNetwork::SignalClose(identifier, -1), 0); return; case nw_connection_state_cancelled: return; } }).get()); processIncomingData(m_nwConnection.get(), [identifier = m_identifier, connection = m_connection.copyRef(), ip = remoteAddress.ipaddr(), port = remoteAddress.port(), isSTUN = m_isSTUN](auto&& buffer) mutable { return WebRTC::extractMessages(WTFMove(buffer), isSTUN ? WebRTC::MessageType::STUN : WebRTC::MessageType::Data, [&](auto* message, auto size) { IPC::DataReference data(message, size); connection->send(Messages::LibWebRTCNetwork::SignalReadPacket { identifier, data, RTCNetwork::IPAddress(ip), port, rtc::TimeMillis() * 1000 }, 0); }); }); nw_connection_start(m_nwConnection.get()); } void NetworkRTCTCPSocketCocoa::close() { if (m_nwConnection) nw_connection_cancel(m_nwConnection.get()); m_rtcProvider.takeSocket(m_identifier); } void NetworkRTCTCPSocketCocoa::setOption(int, int) { // FIXME: Validate this is not needed. } static RetainPtr dataFromVector(Vector&& v) { auto bufferSize = v.size(); auto rawPointer = v.releaseBuffer().leakPtr(); return adoptNS(dispatch_data_create(rawPointer, bufferSize, dispatch_get_main_queue(), ^{ fastFree(rawPointer); })); } Vector NetworkRTCTCPSocketCocoa::createMessageBuffer(const uint8_t* data, size_t size) { if (size >= std::numeric_limits::max()) return { }; if (m_isSTUN) { auto messageLengths = WebRTC::getSTUNOrTURNMessageLengths(data, size); if (!messageLengths) return { }; ASSERT(messageLengths->messageLength == size); ASSERT(messageLengths->messageLengthWithPadding >= size); if (messageLengths->messageLengthWithPadding < size) return { }; Vector buffer; buffer.reserveInitialCapacity(messageLengths->messageLengthWithPadding); buffer.append(data, size); for (size_t cptr = 0 ; cptr < messageLengths->messageLengthWithPadding - size; ++cptr) buffer.uncheckedAppend(0); return buffer; } // Prepend length. Vector buffer; buffer.reserveInitialCapacity(size + 2); buffer.uncheckedAppend((size >> 8) & 0xFF); buffer.uncheckedAppend(size & 0xFF); buffer.append(data, size); return buffer; } void NetworkRTCTCPSocketCocoa::sendTo(const uint8_t* data, size_t size, const rtc::SocketAddress&, const rtc::PacketOptions& options) { auto buffer = createMessageBuffer(data, size); if (buffer.isEmpty()) return; nw_connection_send(m_nwConnection.get(), dataFromVector(WTFMove(buffer)).get(), NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, makeBlockPtr([identifier = m_identifier, connection = m_connection.copyRef(), options](_Nullable nw_error_t) { connection->send(Messages::LibWebRTCNetwork::SignalSentPacket { identifier, options.packet_id, rtc::TimeMillis() }, 0); }).get()); } } // namespace WebKit #endif // USE(LIBWEBRTC) && PLATFORM(COCOA)