---
title: llarp/quic/endpoint.hpp

---

# llarp/quic/endpoint.hpp



## Namespaces

| Name           |
| -------------- |
| **[llarp](Namespaces/namespacellarp.md)** <br>[crypto.hpp] |
| **[llarp::quic](Namespaces/namespacellarp_1_1quic.md)**  |

## Classes

|                | Name           |
| -------------- | -------------- |
| class | **[llarp::quic::Endpoint](Classes/classllarp_1_1quic_1_1Endpoint.md)** <br>QUIC Tunnel [Endpoint](); this is the class that implements either end of a quic tunnel for both servers and clients.  |




## Source code

```cpp
#pragma once

#include "address.hpp"
#include "connection.hpp"
#include "io_result.hpp"
#include "null_crypto.hpp"
#include "packet.hpp"
#include "stream.hpp"
#include <llarp/net/ip_packet.hpp>

#include <chrono>
#include <map>
#include <memory>
#include <queue>
#include <unordered_map>
#include <vector>

#include <uvw/async.h>
#include <uvw/timer.h>

namespace llarp
{
  class EndpointBase;
}  // namespace llarp

namespace llarp::quic
{
  using namespace std::literals;

  inline constexpr auto IDLE_TIMEOUT = 5min;

  inline constexpr std::byte CLIENT_TO_SERVER{1};
  inline constexpr std::byte SERVER_TO_CLIENT{2};

  class Endpoint
  {
   public:
    void
    receive_packet(Address addr, uint8_t ecn, const uint8_t second_pktnum_byte, bstring_view data);

    std::shared_ptr<uvw::Loop>
    get_loop();

   protected:
    EndpointBase& service_endpoint;

    Address local_addr{in6addr_any};

    std::shared_ptr<uvw::TimerHandle> expiry_timer;

    std::vector<std::byte> buf;
    // Max theoretical size of a UDP packet is 2^16-1 minus IP/UDP header overhead
    static constexpr size_t max_buf_size = 64 * 1024;
    // Max size of a UDP packet that we'll send
    static constexpr size_t max_pkt_size_v4 = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
    static constexpr size_t max_pkt_size_v6 = NGTCP2_MAX_UDP_PAYLOAD_SIZE;

    using primary_conn_ptr = std::shared_ptr<Connection>;
    using alias_conn_ptr = std::weak_ptr<Connection>;

    // Connections.  When a client establishes a new connection it chooses its own source connection
    // ID and a destination connection ID and sends them to the server.
    //
    // This container stores the primary Connection instance as a shared_ptr, and any connection
    // aliases as weak_ptrs referencing the primary instance (so that we don't have to double a
    // double-hash lookup on incoming packets, since those frequently use aliases).
    //
    // The destination connection ID should be entirely random and can be up to 160 bits, but the
    // source connection ID does not have to be (i.e. it can encode some information, if desired).
    //
    // The server is going to include in the response:
    // - destination connection ID equal to the client's source connection ID
    // - a new random source connection ID.  (We don't use the client's destination ID but generate
    // our own).  Like the clients source ID, this can contain embedded info.
    //
    // The client stores this, and so we end up with client-scid == server-dcid, and client-dcid ==
    // server-scid, where each side chose its own source connection ID.
    //
    // Ultimately, we store here our own {source connection ID -> Connection} pairs (or
    // equivalently, on incoming packets, the key will be the packet's dest conn ID).
    std::unordered_map<ConnectionID, std::variant<primary_conn_ptr, alias_conn_ptr>> conns;

    using conns_iterator = decltype(conns)::iterator;

    // Connections that are draining (i.e. we are dropping, but need to keep around for a while
    // to catch and drop lagged packets).  The time point is the scheduled removal time.
    std::queue<std::pair<ConnectionID, std::chrono::steady_clock::time_point>> draining;

    NullCrypto null_crypto;

    // Random data that we hash together with a CID to make a stateless reset token
    std::array<std::byte, 32> static_secret;

    friend class Connection;

    explicit Endpoint(EndpointBase& service_endpoint_);

    virtual ~Endpoint();

    // Called to handle an incoming packet
    void
    handle_packet(const Packet& p, const uint8_t second_pktnum_byte);

    // Internal method: handles initial common packet decoding, returns the connection ID or nullopt
    // if decoding failed.
    std::optional<ConnectionID>
    handle_packet_init(const Packet& p);
    // Internal method: handles a packet sent to the given connection
    void
    handle_conn_packet(Connection& c, const Packet& p, const uint8_t second_pktnum_byte);

    // Accept a new incoming connection, i.e. pre-handshake.  Returns a nullptr if the connection
    // can't be created (e.g. because of invalid initial data), or if incoming connections are not
    // accepted by this endpoint (i.e. because it is not a Server instance, or because there are no
    // registered listen handlers).  The base class default returns nullptr.
    virtual std::shared_ptr<Connection>
    accept_initial_connection(const Packet&)
    {
      return nullptr;
    }

    // Reads a packet and handles various error conditions.  Returns an io_result.  Note that it is
    // possible for the conn_it to be erased from `conns` if the error code is anything other than
    // success (0) or NGTCP2_ERR_RETRY.
    io_result
    read_packet(const Packet& p, Connection& conn);

    // Writes the lokinet packet header to the beginning of `buf_`; the header is prepend to quic
    // packets to identify which quic server the packet should be delivered to and consists of:
    // - type [1 byte]: 1 for client->server packets; 2 for server->client packets (other values
    // reserved)
    // - port [2 bytes, network order]: client pseudoport (i.e. either a source or destination port
    // depending on type)
    // - ecn value [1 byte]: provided by ngtcp2.  (Only the lower 2 bits are actually used).
    // - pktnum check byte [1 byte]: included to work around an issue where sometimes rather old
    // packets would be received and due to quic's packet number encoding ngtcp2 would incorrectly
    // think they were from the future and cause a protocol error.
    //
    // \param psuedo_port - the remote's pseudo-port (will be 0 if the remote is a server, > 0 for
    // a client remote)
    // \param ecn - the ecn value from ngtcp2
    // \param second_pktnum_byte - the second-least-significant byte of the quic packet number
    //
    // Returns the number of bytes written to buf_.
    virtual size_t
    write_packet_header(nuint16_t pseudo_port, uint8_t ecn, uint8_t second_pktnum_byte) = 0;

    // Sends a packet to `to` containing `data`. Returns a non-error io_result on success,
    // an io_result with .error_code set to the errno of the failure on failure.
    io_result
    send_packet(const Address& to, bstring_view data, uint8_t ecn, uint8_t second_pktnum_byte);

    // Wrapper around the above that takes a regular std::string_view (i.e. of chars) and recasts
    // it to an string_view of std::bytes.
    io_result
    send_packet(const Address& to, std::string_view data, uint8_t ecn, uint8_t second_pktnum_byte)
    {
      return send_packet(
          to, bstring_view{reinterpret_cast<const std::byte*>(data.data()), data.size()}, ecn, second_pktnum_byte);
    }

    // Another wrapper taking a vector
    io_result
    send_packet(const Address& to, const std::vector<std::byte>& data, uint8_t ecn, uint8_t second_pktnum_byte)
    {
      return send_packet(to, bstring_view{data.data(), data.size()}, ecn, second_pktnum_byte);
    }

    void
    send_version_negotiation(const ngtcp2_version_cid& vi, const Address& source);

    // Looks up a connection. Returns a shared_ptr (either copied for a primary connection, or
    // locked from an alias's weak pointer) if the connection was found or nullptr if not; and a
    // bool indicating whether this connection ID was an alias (true) or not (false).  [Note: the
    // alias value can be true even if the shared_ptr is null in the case of an expired alias that
    // hasn't yet been cleaned up].
    std::pair<std::shared_ptr<Connection>, bool>
    get_conn(const ConnectionID& cid);

    // Called to start closing (or continue closing) a connection by sending a connection close
    // response to any incoming packets.
    //
    // Takes the iterator to the connection pair from `conns` and optional error parameters: if
    // `application` is false (the default) then we do a hard connection close because of transport
    // error, if true we do a graceful application close. `close_reason` can be provided for
    // propagating reason for close to remote, defaults to empty string. For application closes the
    // code is application-defined; for hard closes the code should be one of the NGTCP2_*_ERROR
    // values.
    void
    close_connection(
        Connection& conn, int code = NGTCP2_NO_ERROR, std::string_view close_reason = ""sv);

    void
    start_draining(Connection& conn);

    void
    check_timeouts();

    bool
    delete_conn(const ConnectionID& cid);

    void
    clean_alias_conns();

    ConnectionID
    add_connection_id(Connection& conn, size_t cid_length = ConnectionID::max_size());

   public:
    // Makes a deterministic stateless reset token for the given connection ID. Writes it to dest
    // (which must have NGTCP2_STATELESS_RESET_TOKENLEN bytes available).
    void
    make_stateless_reset_token(const ConnectionID& cid, unsigned char* dest);

    // Default stream buffer size for streams opened through this endpoint.
    size_t default_stream_buffer_size = 64 * 1024;

    // Packet buffer we use when constructing custom packets to fire over lokinet
    std::array<std::byte, net::IPPacket::MaxSize> buf_;

    // Non-copyable, non-movable
    Endpoint(const Endpoint&) = delete;
    Endpoint(Endpoint&&) = delete;
  };

}  // namespace llarp::quic
```


-------------------------------

Updated on 2023-06-06 at 21:47:58 +0000
