Skip to content

Commit 6de8884

Browse files
committed
Initial commit; WIP, not yet functional
0 parents  commit 6de8884

19 files changed

+1593
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build*/
2+
.cache/
3+
compile_commands.json

CMakeLists.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
4+
5+
find_program(CCACHE_PROGRAM ccache)
6+
if(CCACHE_PROGRAM)
7+
foreach(lang C CXX)
8+
if(NOT DEFINED CMAKE_${lang}_COMPILER_LAUNCHER AND NOT CMAKE_${lang}_COMPILER MATCHES ".*/ccache")
9+
message(STATUS "Enabling ccache for ${lang}")
10+
set(CMAKE_${lang}_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE STRING "")
11+
endif()
12+
endforeach()
13+
endif()
14+
15+
project(plainquic
16+
VERSION 0.0.1
17+
DESCRIPTION "QUIC test implementation"
18+
LANGUAGES C CXX)
19+
20+
set(CMAKE_CXX_STANDARD 17)
21+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
22+
23+
set(NGTCP2_DIR "${PROJECT_SOURCE_DIR}/../build" CACHE "PATH" "Path to built ngtpc2 build dir")
24+
25+
add_library(quictest
26+
address.cpp
27+
client.cpp
28+
connection.cpp
29+
endpoint.cpp
30+
server.cpp
31+
)
32+
33+
add_library(ngtcp2 STATIC IMPORTED GLOBAL)
34+
set_target_properties(ngtcp2 PROPERTIES IMPORTED_LOCATION ${NGTCP2_DIR}/lib/libngtcp2.a)
35+
target_include_directories(ngtcp2 INTERFACE ${NGTCP2_DIR}/lib/includes ${NGTCP2_DIR}/../lib/includes)
36+
37+
find_package(PkgConfig REQUIRED)
38+
pkg_check_modules(LIBUV libuv>=1.18.0 IMPORTED_TARGET)
39+
pkg_check_modules(OXENMQ liboxenmq>=1.2.3 IMPORTED_TARGET)
40+
41+
target_link_libraries(quictest PUBLIC ngtcp2 PkgConfig::LIBUV PkgConfig::OXENMQ)
42+
43+
add_executable(echo-server echo-server.cpp)
44+
add_executable(echo-client echo-client.cpp)
45+
46+
target_link_libraries(echo-server quictest)
47+
target_link_libraries(echo-client quictest)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# WIP - liblokinet QUIC-based, plaintext tunnel

address.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "address.h"
2+
3+
extern "C" {
4+
#include <arpa/inet.h>
5+
}
6+
7+
#include <iostream>
8+
9+
namespace quic {
10+
11+
using namespace std::literals;
12+
13+
Address::Address(std::array<uint8_t, 4> ip, uint16_t port) {
14+
s.in.sin_family = AF_INET;
15+
std::memcpy(&s.in.sin_addr.s_addr, ip.data(), ip.size());
16+
s.in.sin_port = htons(port);
17+
a.addrlen = sizeof(s.in);
18+
}
19+
20+
Address::Address(const sockaddr_any* addr, size_t addrlen) {
21+
assert(addrlen == sizeof(sockaddr_in)); // FIXME: IPv6 support
22+
std::memmove(&s, addr, addrlen);
23+
a.addrlen = addrlen;
24+
}
25+
Address& Address::operator=(const Address& addr) {
26+
std::memmove(&s, &addr.s, sizeof(s));
27+
a.addrlen = addr.a.addrlen;
28+
return *this;
29+
}
30+
31+
std::string Address::to_string() const {
32+
if (a.addrlen != sizeof(sockaddr_in))
33+
return "(unknown-addr)";
34+
char buf[INET_ADDRSTRLEN] = {0};
35+
inet_ntop(AF_INET, &s.in.sin_addr, buf, INET_ADDRSTRLEN);
36+
return buf + ":"s + std::to_string(ntohs(s.in.sin_port));
37+
}
38+
39+
std::ostream& operator<<(std::ostream& o, const Address& a) {
40+
return o << a.to_string();
41+
}
42+
std::ostream& operator<<(std::ostream& o, const Path& p) {
43+
return o << p.local << "<-" << p.remote;
44+
}
45+
46+
}

address.h

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <cassert>
5+
#include <cstring>
6+
#include <string>
7+
#include <iosfwd>
8+
9+
#include <ngtcp2/ngtcp2.h>
10+
11+
extern "C" {
12+
#include <netinet/in.h>
13+
#include <sys/socket.h>
14+
}
15+
16+
namespace quic {
17+
18+
union sockaddr_any {
19+
sockaddr_storage storage;
20+
sockaddr sa;
21+
sockaddr_in6 in6;
22+
sockaddr_in in;
23+
};
24+
25+
26+
class Address {
27+
sockaddr_any s{};
28+
ngtcp2_addr a{0, &s.sa, nullptr};
29+
public:
30+
Address() = default;
31+
Address(std::array<uint8_t, 4> ip, uint16_t port);
32+
Address(const sockaddr_any* addr, size_t addrlen);
33+
Address(const Address& addr) {
34+
*this = addr;
35+
}
36+
Address& operator=(const Address& addr);
37+
38+
// Implicit conversion to sockaddr* and ngtcp2_addr& so that an Address can be passed wherever
39+
// one of those is expected.
40+
operator sockaddr*() { return a.addr; }
41+
operator const sockaddr*() const { return a.addr; }
42+
constexpr socklen_t sockaddr_size() const { return a.addrlen; }
43+
operator ngtcp2_addr&() { return a; }
44+
operator const ngtcp2_addr&() const { return a; }
45+
46+
std::string to_string() const;
47+
};
48+
49+
// Wraps an ngtcp2_path (which is basically just and address pair) with remote/local components.
50+
// Implicitly convertable to a ngtcp2_path* so that this can be passed wherever a ngtcp2_path* is
51+
// taken in the ngtcp2 API.
52+
struct Path {
53+
Address local{}, remote{};
54+
ngtcp2_path path{
55+
{0, local, nullptr},
56+
{0, remote, nullptr}};
57+
58+
Path() = default;
59+
Path(const Address& local, const Address& remote) : local{local}, remote{remote} {}
60+
Path(const Address& local, const sockaddr_any* remote_addr, size_t remote_len)
61+
: local{local}, remote{remote_addr, remote_len} {}
62+
Path(const Path& p) : local{p.local}, remote{p.remote} {}
63+
64+
Path& operator=(const Path& p) {
65+
local = p.local;
66+
remote = p.remote;
67+
return *this;
68+
}
69+
70+
// Equivalent to `&obj.path`, but slightly more convenient for passing into ngtcp2 functions
71+
// taking a ngtcp2_path pointer.
72+
operator ngtcp2_path*() { return &path; }
73+
operator const ngtcp2_path*() const { return &path; }
74+
75+
std::string to_string() const;
76+
};
77+
78+
std::ostream& operator<<(std::ostream& o, const Address& a);
79+
std::ostream& operator<<(std::ostream& o, const Path& p);
80+
81+
}

client.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
#include "client.h"
3+
#include "log.h"
4+
5+
namespace quic {
6+
7+
Client::Client(Address remote, uv_loop_t* loop, std::optional<Address> local)
8+
: Endpoint{std::move(local), loop} {
9+
// Our UDP socket is now set up, so now we initiate contact with the remote QUIC
10+
Debug("Connecting to ", remote);
11+
12+
// TODO: need timers for:
13+
//
14+
// - timeout (to disconnect if idle for too longer)
15+
//
16+
// - probably don't need for lokinet tunnel: change local addr -- attempts to re-bind the local socket
17+
//
18+
// - key_update_timer
19+
//
20+
// - delay_stream_timer
21+
22+
23+
conns_iterator it = conns.end();
24+
try {
25+
auto* s = dynamic_cast<Server*>(this);
26+
assert(s);
27+
auto [insit, ins] = conns.emplace(std::piecewise_construct,
28+
std::forward_as_tuple(local_cid),
29+
std::forward_as_tuple(*s, local_cid, hd, p.path));
30+
if (!ins)
31+
Warn("Internal error: duplicate connection id?");
32+
else
33+
it = insit;
34+
} catch (const std::exception& e) {
35+
Warn("Failed to create Connection: ", e.what());
36+
}
37+
38+
39+
}
40+
41+
void Client::handle_packet(const Packet& p) {
42+
version_info vi;
43+
auto rv = ngtcp2_pkt_decode_version_cid(&vi.version, &vi.dcid, &vi.dcid_len, &vi.scid, &vi.scid_len,
44+
reinterpret_cast<const uint8_t*>(p.data.data()), p.data.size(), NGTCP2_MAX_CIDLEN);
45+
if (rv == 1) // 1 means Version Negotiation should be sent
46+
return send_version_negotiation(vi, p.path.remote);
47+
else if (rv != 0) {
48+
Warn("QUIC packet header decode failed: ", ngtcp2_strerror(rv));
49+
return;
50+
}
51+
52+
if (vi.dcid_len > ConnectionID::max_size()) {
53+
Warn("Internal error: destination ID is longer than should be allowed");
54+
return;
55+
}
56+
57+
}

client.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include "endpoint.h"
4+
5+
#include <optional>
6+
7+
namespace quic {
8+
9+
class Client : public Endpoint {
10+
public:
11+
// Constructs a client that establishes an outgoing connection to `remote`. `local` can be used
12+
// to optionally bind to a local IP and/or port for the connection.
13+
Client(Address remote, uv_loop_t* loop, std::optional<Address> local = std::nullopt);
14+
15+
private:
16+
void handle_packet(const Packet& p) override;
17+
};
18+
19+
}

0 commit comments

Comments
 (0)