| // Copyright 2019 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_ | |
| #define QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_ | |
| #include <cstdint> | |
| #include <list> | |
| #include <optional> | |
| #include <string> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/core/crypto/quic_crypto_client_config.h" | |
| #include "quiche/quic/core/frames/quic_connection_close_frame.h" | |
| #include "quiche/quic/core/http/http_frames.h" | |
| #include "quiche/quic/core/http/quic_spdy_client_session.h" | |
| #include "quiche/quic/core/http/quic_spdy_client_stream.h" | |
| #include "quiche/quic/core/http/quic_spdy_session.h" | |
| #include "quiche/quic/core/http/quic_spdy_stream.h" | |
| #include "quiche/quic/core/quic_config.h" | |
| #include "quiche/quic/core/quic_error_codes.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/core/quic_versions.h" | |
| #include "quiche/quic/masque/masque_utils.h" | |
| #include "quiche/quic/platform/api/quic_export.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| #include "quiche/quic/platform/api/quic_socket_address.h" | |
| #include "quiche/quic/tools/quic_url.h" | |
| #include "quiche/common/capsule.h" | |
| #include "quiche/common/http/http_header_block.h" | |
| #include "quiche/common/quiche_ip_address.h" | |
| namespace quic { | |
| // QUIC client session for connection to MASQUE proxy. This session establishes | |
| // a connection to a MASQUE proxy and handles sending and receiving DATAGRAM | |
| // frames for operation of the MASQUE protocol. Multiple end-to-end encapsulated | |
| // sessions can then coexist inside this session. Once these are created, they | |
| // need to be registered with this session. | |
| class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession, | |
| public QuicSpdyStream::Visitor { | |
| public: | |
| using ContextId = uint64_t; | |
| // Interface meant to be implemented by the owner of the | |
| // MasqueClientSession instance. | |
| class QUIC_NO_EXPORT Owner { | |
| public: | |
| virtual ~Owner() {} | |
| // Notifies the owner that a settings frame has been received. | |
| virtual void OnSettingsReceived() = 0; | |
| }; | |
| // Interface meant to be implemented by client sessions encapsulated inside | |
| // CONNECT-UDP, i.e. the end-to-end QUIC client sessions that run inside | |
| // CONNECT-UDP encapsulation. | |
| class QUIC_NO_EXPORT EncapsulatedClientSession { | |
| public: | |
| virtual ~EncapsulatedClientSession() {} | |
| // Process UDP packet that was just decapsulated. |packet| contains the UDP | |
| // payload. | |
| virtual void ProcessPacket(absl::string_view packet, | |
| QuicSocketAddress target_server_address) = 0; | |
| // Close the encapsulated connection. | |
| virtual void CloseConnection( | |
| QuicErrorCode error, const std::string& details, | |
| ConnectionCloseBehavior connection_close_behavior) = 0; | |
| }; | |
| // Interface meant to be implemented by client sessions encapsulated inside | |
| // CONNECT-IP, i.e. the end-to-end QUIC client sessions that run inside | |
| // CONNECT-IP encapsulation. | |
| class QUIC_NO_EXPORT EncapsulatedIpSession { | |
| public: | |
| virtual ~EncapsulatedIpSession() {} | |
| // Process packet that was just decapsulated. |packet| contains the IP | |
| // header and payload. | |
| virtual void ProcessIpPacket(absl::string_view packet) = 0; | |
| // Close the encapsulated connection. | |
| virtual void CloseIpSession(const std::string& details) = 0; | |
| virtual bool OnAddressAssignCapsule( | |
| const quiche::AddressAssignCapsule& capsule) = 0; | |
| virtual bool OnAddressRequestCapsule( | |
| const quiche::AddressRequestCapsule& capsule) = 0; | |
| virtual bool OnRouteAdvertisementCapsule( | |
| const quiche::RouteAdvertisementCapsule& capsule) = 0; | |
| virtual bool OnDnsAssignCapsule(const DnsAssignCapsule& /*capsule*/) { | |
| return true; | |
| } | |
| virtual bool OnPref64Capsule(const Pref64Capsule& /*capsule*/) { | |
| return true; | |
| } | |
| }; | |
| // CONNECT-ETHERNET. | |
| class QUIC_NO_EXPORT EncapsulatedEthernetSession { | |
| public: | |
| virtual ~EncapsulatedEthernetSession() {} | |
| // Process packet that was just decapsulated. |frame| contains the | |
| // Ethernet header and payload. | |
| virtual void ProcessEthernetFrame(absl::string_view frame) = 0; | |
| // Close the encapsulated connection. | |
| virtual void CloseEthernetSession(const std::string& details) = 0; | |
| }; | |
| // Constructor for when this is only an underlying session. | |
| // Takes ownership of |connection|, but not of |crypto_config| or |owner|. | |
| // All pointers must be non-null. Caller must ensure that |owner| stays valid | |
| // for the lifetime of the newly created MasqueClientSession. | |
| MasqueClientSession(MasqueMode masque_mode, const std::string& uri_template, | |
| const QuicConfig& config, | |
| const ParsedQuicVersionVector& supported_versions, | |
| QuicConnection* connection, const QuicServerId& server_id, | |
| QuicCryptoClientConfig* crypto_config, Owner* owner); | |
| // Constructor for when this is only an encapsulated session. | |
| MasqueClientSession(const QuicConfig& config, | |
| const ParsedQuicVersionVector& supported_versions, | |
| QuicConnection* connection, const QuicServerId& server_id, | |
| QuicCryptoClientConfig* crypto_config, Owner* owner); | |
| // Disallow copy and assign. | |
| MasqueClientSession(const MasqueClientSession&) = delete; | |
| MasqueClientSession& operator=(const MasqueClientSession&) = delete; | |
| // From QuicSession. | |
| void OnDatagramAcked(QuicDatagramId datagram_id) override; | |
| void OnDatagramLost(QuicDatagramId datagram_id) override; | |
| void OnConnectionClosed(const QuicConnectionCloseFrame& frame, | |
| ConnectionCloseSource source) override; | |
| void OnStreamClosed(QuicStreamId stream_id) override; | |
| // From QuicSpdySession. | |
| bool OnSettingsFrame(const SettingsFrame& frame) override; | |
| // Send encapsulated UDP packet. |packet| contains the UDP payload. | |
| void SendPacket(absl::string_view packet, | |
| const QuicSocketAddress& target_server_address, | |
| EncapsulatedClientSession* encapsulated_client_session); | |
| // Send CONNECT-UDP-BIND packet, creating contexts if needed. | |
| void SendConnectUdpBindPacket( | |
| absl::string_view packet, const QuicSocketAddress& target_server_address, | |
| EncapsulatedClientSession* encapsulated_client_session); | |
| // Request uncompressed context for CONNECT-UDP Bind. | |
| void set_bind_use_uncompressed_context(bool use_uncompressed_context) { | |
| QUICHE_DCHECK(masque_mode_ == MasqueMode::kConnectUdpBind); | |
| bind_use_uncompressed_context_ = use_uncompressed_context; | |
| } | |
| // Send encapsulated IP packet. |packet| contains the IP header and payload. | |
| void SendIpPacket(absl::string_view packet, | |
| EncapsulatedIpSession* encapsulated_ip_session); | |
| // Send DNS_ASSIGN capsule over CONNECT-IP stream. | |
| void SendDnsAssignCapsule(const DnsAssignCapsule& capsule, | |
| EncapsulatedIpSession* encapsulated_ip_session); | |
| // Send PREF64 capsule over CONNECT-IP stream. | |
| void SendPref64Capsule(const Pref64Capsule& capsule, | |
| EncapsulatedIpSession* encapsulated_ip_session); | |
| // Send encapsulated Ethernet frame. |frame| contains the Ethernet | |
| // header and payload. | |
| void SendEthernetFrame( | |
| absl::string_view frame, | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session); | |
| // Close CONNECT-UDP stream tied to this encapsulated client session. | |
| void CloseConnectUdpStream( | |
| EncapsulatedClientSession* encapsulated_client_session); | |
| // Close CONNECT-IP stream tied to this encapsulated client session. | |
| void CloseConnectIpStream(EncapsulatedIpSession* encapsulated_ip_session); | |
| // Close CONNECT-ETHERNET stream tied to this encapsulated client session. | |
| void CloseConnectEthernetStream( | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session); | |
| // Generate a random Unique Local Address and register a mapping from | |
| // that address to the corresponding hostname. The returned address should be | |
| // removed by calling RemoveFakeAddress() once it is no longer needed. | |
| quiche::QuicheIpAddress GetFakeAddress(absl::string_view hostname); | |
| // Removes a fake address that was previously created by GetFakeAddress(). | |
| void RemoveFakeAddress(const quiche::QuicheIpAddress& fake_address); | |
| // Set additional HTTP headers that will be sent on all requests to the MASQUE | |
| // proxy. Separated with colons and semicolons. | |
| // For example: "name1:value1;name2:value2". | |
| void set_additional_headers(absl::string_view additional_headers) { | |
| additional_headers_ = additional_headers; | |
| } | |
| // Send a GET request to the MASQUE proxy itself. | |
| QuicSpdyClientStream* SendGetRequest(absl::string_view path); | |
| // QuicSpdyStream::Visitor | |
| void OnClose(QuicSpdyStream* stream) override; | |
| // Set the concealed auth key ID and private key. key_id MUST be non-empty, | |
| // private_key MUST be ED25519_PRIVATE_KEY_LEN bytes long and public_key MUST | |
| // be ED25519_PUBLIC_KEY_LEN bytes long. | |
| void EnableConcealedAuth(absl::string_view key_id, | |
| absl::string_view private_key, | |
| absl::string_view public_key); | |
| private: | |
| // State that the MasqueClientSession keeps for each CONNECT-UDP request. | |
| class QUIC_NO_EXPORT ConnectUdpClientState | |
| : public QuicSpdyStream::Http3DatagramVisitor, | |
| public QuicSpdyStream::ConnectUdpBindVisitor { | |
| public: | |
| // |stream| and |encapsulated_client_session| must be valid for the lifetime | |
| // of the ConnectUdpClientState. | |
| explicit ConnectUdpClientState( | |
| QuicSpdyClientStream* stream, | |
| EncapsulatedClientSession* encapsulated_client_session, | |
| MasqueClientSession* masque_session, | |
| const QuicSocketAddress& target_server_address); | |
| ~ConnectUdpClientState(); | |
| void set_is_bind(bool is_bind) { is_bind_ = is_bind; } | |
| bool is_bind() const { return is_bind_; } | |
| // Disallow copy but allow move. | |
| ConnectUdpClientState(const ConnectUdpClientState&) = delete; | |
| ConnectUdpClientState(ConnectUdpClientState&&); | |
| ConnectUdpClientState& operator=(const ConnectUdpClientState&) = delete; | |
| ConnectUdpClientState& operator=(ConnectUdpClientState&&); | |
| QuicSpdyClientStream* stream() const { return stream_; } | |
| EncapsulatedClientSession* encapsulated_client_session() const { | |
| return encapsulated_client_session_; | |
| } | |
| const QuicSocketAddress& target_server_address() const { | |
| return target_server_address_; | |
| } | |
| void CloseContext(ContextId context_id); | |
| // From QuicSpdyStream::Http3DatagramVisitor. | |
| void OnHttp3Datagram(QuicStreamId stream_id, | |
| absl::string_view payload) override; | |
| void OnUnknownCapsule(QuicStreamId /*stream_id*/, | |
| const quiche::UnknownCapsule& /*capsule*/) override {} | |
| // From QuicSpdyStream::ConnectUdpBindVisitor. | |
| bool OnCompressionAssignCapsule( | |
| const quiche::CompressionAssignCapsule& capsule) override; | |
| bool OnCompressionCloseCapsule( | |
| const quiche::CompressionCloseCapsule& capsule) override; | |
| ContextId AllocateContextId(); | |
| // Get the context id for the given address if it exists or if | |
| // an uncompressed context exists. | |
| std::optional<ContextId> GetContextForAddress( | |
| const QuicSocketAddress& target_server_address); | |
| ContextId CreateCompressedContext(const QuicSocketAddress& target_address); | |
| ContextId CreateUncompressedContext(); | |
| std::optional<std::string> PrepareBindPacket( | |
| absl::string_view packet, const QuicSocketAddress& target_address, | |
| bool use_uncompressed_context); | |
| private: | |
| QuicSpdyClientStream* stream_; // Unowned. | |
| EncapsulatedClientSession* encapsulated_client_session_; // Unowned. | |
| MasqueClientSession* masque_session_; // Unowned. | |
| QuicSocketAddress target_server_address_; | |
| ContextId next_available_context_id_ = 2; | |
| absl::flat_hash_map<int, QuicSocketAddress> bind_context_ip_map_ = {}; | |
| bool is_bind_ = false; | |
| }; | |
| // State that the MasqueClientSession keeps for each CONNECT-IP request. | |
| class QUIC_NO_EXPORT ConnectIpClientState | |
| : public QuicSpdyStream::Http3DatagramVisitor, | |
| public QuicSpdyStream::ConnectIpVisitor { | |
| public: | |
| // |stream| and |encapsulated_client_session| must be valid for the lifetime | |
| // of the ConnectUdpClientState. | |
| explicit ConnectIpClientState( | |
| QuicSpdyClientStream* stream, | |
| EncapsulatedIpSession* encapsulated_ip_session, | |
| MasqueClientSession* masque_session); | |
| ~ConnectIpClientState(); | |
| // Disallow copy but allow move. | |
| ConnectIpClientState(const ConnectIpClientState&) = delete; | |
| ConnectIpClientState(ConnectIpClientState&&); | |
| ConnectIpClientState& operator=(const ConnectIpClientState&) = delete; | |
| ConnectIpClientState& operator=(ConnectIpClientState&&); | |
| QuicSpdyClientStream* stream() const { return stream_; } | |
| EncapsulatedIpSession* encapsulated_ip_session() const { | |
| return encapsulated_ip_session_; | |
| } | |
| // From QuicSpdyStream::Http3DatagramVisitor. | |
| void OnHttp3Datagram(QuicStreamId stream_id, | |
| absl::string_view payload) override; | |
| void OnUnknownCapsule(QuicStreamId stream_id, | |
| const quiche::UnknownCapsule& capsule) override; | |
| // From QuicSpdyStream::ConnectIpVisitor. | |
| bool OnAddressAssignCapsule( | |
| const quiche::AddressAssignCapsule& capsule) override; | |
| bool OnAddressRequestCapsule( | |
| const quiche::AddressRequestCapsule& capsule) override; | |
| bool OnRouteAdvertisementCapsule( | |
| const quiche::RouteAdvertisementCapsule& capsule) override; | |
| void OnHeadersWritten() override; | |
| private: | |
| QuicSpdyClientStream* stream_; // Unowned. | |
| EncapsulatedIpSession* encapsulated_ip_session_; // Unowned. | |
| MasqueClientSession* masque_session_; // Unowned. | |
| }; | |
| // State that the MasqueClientSession keeps for each CONNECT-ETHERNET request. | |
| class QUIC_NO_EXPORT ConnectEthernetClientState | |
| : public QuicSpdyStream::Http3DatagramVisitor { | |
| public: | |
| // |stream| and |encapsulated_client_session| must be valid for the lifetime | |
| // of the ConnectUdpClientState. | |
| explicit ConnectEthernetClientState( | |
| QuicSpdyClientStream* stream, | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session, | |
| MasqueClientSession* masque_session); | |
| ~ConnectEthernetClientState(); | |
| // Disallow copy but allow move. | |
| ConnectEthernetClientState(const ConnectEthernetClientState&) = delete; | |
| ConnectEthernetClientState(ConnectEthernetClientState&&); | |
| ConnectEthernetClientState& operator=(const ConnectEthernetClientState&) = | |
| delete; | |
| ConnectEthernetClientState& operator=(ConnectEthernetClientState&&); | |
| QuicSpdyClientStream* stream() const { return stream_; } | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session() const { | |
| return encapsulated_ethernet_session_; | |
| } | |
| // From QuicSpdyStream::Http3DatagramVisitor. | |
| void OnHttp3Datagram(QuicStreamId stream_id, | |
| absl::string_view payload) override; | |
| void OnUnknownCapsule(QuicStreamId /*stream_id*/, | |
| const quiche::UnknownCapsule& /*capsule*/) override {} | |
| private: | |
| QuicSpdyClientStream* stream_; // Unowned. | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session_; // Unowned. | |
| MasqueClientSession* masque_session_; // Unowned. | |
| }; | |
| HttpDatagramSupport LocalHttpDatagramSupport() override { | |
| return HttpDatagramSupport::kRfc; | |
| } | |
| // target_server_address should be set to empty | |
| // to create state for bind. | |
| ConnectUdpClientState* GetOrCreateConnectUdpClientState( | |
| const QuicSocketAddress& target_server_address, | |
| EncapsulatedClientSession* encapsulated_client_session); | |
| // Send Capsule for CONNECT-UDP Bind. | |
| void SendBindCapsule(const quiche::Capsule& capsule, | |
| EncapsulatedClientSession* encapsulated_client_session); | |
| const ConnectIpClientState* GetOrCreateConnectIpClientState( | |
| EncapsulatedIpSession* encapsulated_ip_session); | |
| const ConnectEthernetClientState* GetOrCreateConnectEthernetClientState( | |
| EncapsulatedEthernetSession* encapsulated_ethernet_session); | |
| std::optional<std::string> ComputeConcealedAuthHeader(const QuicUrl& url); | |
| void AddAdditionalHeaders(quiche::HttpHeaderBlock& headers, | |
| const QuicUrl& url); | |
| bool IsBind() const { return masque_mode_ == MasqueMode::kConnectUdpBind; } | |
| MasqueMode masque_mode_ = MasqueMode::kInvalid; | |
| std::string uri_template_; | |
| std::string additional_headers_; | |
| std::string concealed_auth_key_id_; | |
| std::string concealed_auth_private_key_; | |
| std::string concealed_auth_public_key_; | |
| std::list<ConnectUdpClientState> connect_udp_client_states_; | |
| bool bind_use_uncompressed_context_ = false; | |
| std::list<ConnectIpClientState> connect_ip_client_states_; | |
| std::list<ConnectEthernetClientState> connect_ethernet_client_states_; | |
| // Maps fake addresses generated by GetFakeAddress() to their corresponding | |
| // hostnames. | |
| absl::flat_hash_map<std::string, std::string> fake_addresses_; | |
| Owner* owner_ = nullptr; // Unowned; | |
| }; | |
| } // namespace quic | |
| #endif // QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_ |