mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-11-15 12:13:24 +00:00
206 lines
8.3 KiB
C++
206 lines
8.3 KiB
C++
#pragma once
|
|
|
|
#include <llarp/endpoint_base.hpp>
|
|
#include "stream.hpp"
|
|
#include "address.hpp"
|
|
#include "client.hpp"
|
|
#include "server.hpp"
|
|
|
|
#include <charconv>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include <uvw/tcp.h>
|
|
|
|
namespace llarp::quic
|
|
{
|
|
namespace tunnel
|
|
{
|
|
// The server sends back a 0x00 to signal that the remote TCP connection was established and
|
|
// that it is now accepting stream data; the client is not allowed to send any other data down
|
|
// the stream until this comes back (any data sent down the stream before then is discarded.)
|
|
inline constexpr std::byte CONNECT_INIT{0x00};
|
|
// QUIC application error codes we sent on failures:
|
|
// Failure to establish an initial connection:
|
|
inline constexpr uint64_t ERROR_CONNECT{0x5471907};
|
|
// Error if we receive something other than CONNECT_INIT as the initial stream data from the
|
|
// server
|
|
inline constexpr uint64_t ERROR_BAD_INIT{0x5471908};
|
|
// Close error code sent if we get an error on the TCP socket (other than an initial connect
|
|
// failure)
|
|
inline constexpr uint64_t ERROR_TCP{0x5471909};
|
|
|
|
// We pause reading from the local TCP socket if we have more than this amount of outstanding
|
|
// unacked data in the quic tunnel, then resume once it drops below this.
|
|
inline constexpr size_t PAUSE_SIZE = 64 * 1024;
|
|
} // namespace tunnel
|
|
|
|
/// Manager class for incoming and outgoing QUIC tunnels.
|
|
class TunnelManager
|
|
{
|
|
public:
|
|
using ListenHandler = std::function<std::optional<SockAddr>(
|
|
std::string_view lokinet_addr, // The remote's full lokinet address
|
|
uint16_t port // The requested port the tunnel wants to reach
|
|
)>;
|
|
|
|
// Timeout for the next `open()`. Note that when `open()` is given a ONS name to resolve this
|
|
// includes the resolution time.
|
|
std::chrono::milliseconds open_timeout = 4s;
|
|
|
|
TunnelManager(EndpointBase& endpoint);
|
|
|
|
/// Adds an incoming listener callback. When a new incoming quic connection is initiated to us
|
|
/// by some remote we invoke these callback(s) in order of registration. Each one has three
|
|
/// options:
|
|
/// - return a concrete llarp::SockAddr giving the TCP address/port to which we should connect
|
|
/// new incoming streams over the connection.
|
|
/// - returns std::nullopt to decline handling the connection (we will try the next listen
|
|
/// handler, in order of registration).
|
|
/// - throws an exception (derived from std::exception) in which case we refuse the connection
|
|
/// without trying any additional handlers.
|
|
///
|
|
/// If `listen()` is not called at all then new incoming connections will be immediately
|
|
/// dropped.
|
|
///
|
|
/// For plain-C wrappers around this see [FIXME].
|
|
int
|
|
listen(ListenHandler handler);
|
|
|
|
/// Simple wrapper around `listen(...)` that adds a handler that accepts all incoming
|
|
/// connections trying to tunnel to port `port` and maps them to `localhost:port`.
|
|
int
|
|
listen(SockAddr port);
|
|
|
|
/// Removes an incoming connection handler; takes the ID returned by `listen()`.
|
|
void
|
|
forget(int id);
|
|
|
|
/// Called when open succeeds or times out.
|
|
using OpenCallback = std::function<void(bool success)>;
|
|
|
|
/// Opens a quic tunnel to some remote lokinet address. (Should only be called from the event
|
|
/// loop thread.)
|
|
///
|
|
/// \param remote_addr is the lokinet address or ONS name (e.g. `azfojblahblahblah.loki` or
|
|
/// `blocks.loki`) that the tunnel should connect to.
|
|
/// \param port is the tunneled port on the remote that the client wants to reach. (This is
|
|
/// *not* the quic pseudo-port, which is always 0).
|
|
/// \param callback callback invoked when the quic connection has been established, or has timed
|
|
/// out.
|
|
/// \param bind_addr is the bind address and port that we should use for the localhost TCP
|
|
/// connection. Use port 0 to let the OS choose a random high port. Defaults to `127.0.0.1:0`.
|
|
///
|
|
/// This call immediately opens the local TCP socket, and initiates the lokinet connection and
|
|
/// QUIC tunnel to the remote. If the connection fails, the TCP socket will be closed. Note,
|
|
/// however, that this TCP socket will block until the underlying quic connection is
|
|
/// established.
|
|
///
|
|
/// Each connection to the local TCP socket establishes a new stream over the QUIC connection.
|
|
///
|
|
/// \return a pair:
|
|
/// - SockAddr containing the just-opened localhost socket that tunnels to the remote. This is
|
|
/// typically the same IP as `bind_addr`, with the port filled in (if bind_addr had a 0 port).
|
|
/// Note that, while you can connect to this socket immediately, it will block until the actual
|
|
/// connection and streams are established (and will be closed if they fail).
|
|
/// - unique integer that can be passed to close() to stop listening for new connections. This
|
|
/// also serves as a unique internal "pseudo-port" number to route returned quic packets to the
|
|
/// right connection.
|
|
///
|
|
/// TODO: add a callback to invoke when QUIC connection succeeds or fails.
|
|
/// TODO: add a plain C wrapper around this
|
|
std::pair<SockAddr, uint16_t>
|
|
open(
|
|
std::string_view remote_addr,
|
|
uint16_t port,
|
|
OpenCallback on_open = {},
|
|
SockAddr bind_addr = {127, 0, 0, 1});
|
|
|
|
/// Start closing an outgoing tunnel; takes the ID returned by `open()`. Note that an existing
|
|
/// established tunneled connections will not be forcibly closed; this simply stops accepting
|
|
/// new tunnel connections.
|
|
void
|
|
close(int id);
|
|
|
|
/// Called from tun code to deliver a quic packet.
|
|
///
|
|
/// \param dest - the convotag for which the packet arrived
|
|
/// \param buf - the raw arriving packet
|
|
///
|
|
void
|
|
receive_packet(const service::ConvoTag& tag, const llarp_buffer_t& buf);
|
|
|
|
/// return true if we have any listeners added
|
|
inline bool
|
|
hasListeners() const
|
|
{
|
|
return not incoming_handlers_.empty();
|
|
}
|
|
|
|
private:
|
|
EndpointBase& service_endpoint_;
|
|
|
|
struct ClientTunnel
|
|
{
|
|
// quic endpoint
|
|
std::unique_ptr<Client> client;
|
|
// Callback to invoke on quic connection established (true argument) or failed (false arg)
|
|
OpenCallback open_cb;
|
|
// TCP listening socket
|
|
std::shared_ptr<uvw::TCPHandle> tcp;
|
|
// Accepted TCP connections
|
|
std::unordered_set<std::shared_ptr<uvw::TCPHandle>> conns;
|
|
// Queue of incoming connections that are waiting for a stream to become available (either
|
|
// because we are still handshaking, or we reached the stream limit).
|
|
std::queue<std::weak_ptr<uvw::TCPHandle>> pending_incoming;
|
|
|
|
~ClientTunnel();
|
|
};
|
|
|
|
// pseudo-port -> Client instance (the "port" is used to route incoming quic packets to the
|
|
// right quic endpoint); pseudo-ports start at 1.
|
|
std::map<uint16_t, ClientTunnel> client_tunnels_;
|
|
|
|
uint16_t next_pseudo_port_ = 0;
|
|
// bool pport_wrapped_ = false;
|
|
|
|
bool
|
|
continue_connecting(
|
|
uint16_t pseudo_port, bool step_success, std::string_view step_name, std::string_view addr);
|
|
|
|
void
|
|
make_client(const SockAddr& remote, std::pair<const uint16_t, ClientTunnel>& row);
|
|
|
|
void
|
|
flush_pending_incoming(ClientTunnel& ct);
|
|
|
|
// Server instance; this listens on pseudo-port 0 (if it listens). This is automatically
|
|
// instantiated the first time `listen()` is called; if not instantiated we simply drop any
|
|
// inbound client-to-server quic packets.
|
|
std::unique_ptr<Server> server_;
|
|
|
|
void
|
|
make_server();
|
|
|
|
// Called when a new during connection handshaking once we have the established transport
|
|
// parameters (which include the port) if this is an incoming connection (and this endpoint is a
|
|
// server). This checks handlers to see whether the stream is allowed and, if so, returns a
|
|
// SockAddr containing the IP/port the tunnel should map to. Returns nullopt if the connection
|
|
// should be rejected.
|
|
std::optional<SockAddr>
|
|
allow_connection(std::string_view lokinet_addr, uint16_t port);
|
|
|
|
// Incoming stream handlers
|
|
std::map<int, ListenHandler> incoming_handlers_;
|
|
int next_handler_id_ = 1;
|
|
|
|
std::shared_ptr<uvw::Loop>
|
|
get_loop();
|
|
|
|
// Cleanup member
|
|
std::shared_ptr<int> timer_keepalive_ = std::make_shared<int>(0);
|
|
};
|
|
|
|
} // namespace llarp::quic
|