all repos — circe-vpn @ f8438a3f9db13c62e62593c72e846eb3911062af

Circe VPN

Started real work on core
Juniper Beatitudes [email protected]
Sat, 18 Jul 2026 16:32:06 -0500
commit

f8438a3f9db13c62e62593c72e846eb3911062af

parent

bc760844555fd242ce945937d17188a123d79873

5 files changed, 323 insertions(+), 23 deletions(-)

jump to
M core/core.ccore/core.c

@@ -1,7 +1,11 @@

#include "core.h" #include "cbor.h" +#include "chacha20_poly1305.h" +#include "memory.h" #include "packet.h" +#include "peer_table.h" #include <stdbool.h> +#include <stdint.h> #define CIRCE_CORE_ASSERT(x, err) \ do \

@@ -13,11 +17,118 @@ } \

} \ while (false) +static struct circe_peer peer_table[CIRCE_PEER_TABLE_LEN]; + +static bool +peer_table_lookup (uint8_t identity[CIRCE_IDENTITY_LEN], + struct circe_peer **out) +{ + uint8_t start_idx = identity[0] % CIRCE_PEER_TABLE_LEN; + for (size_t i = 0; i < CIRCE_PEER_TABLE_LEN; ++i) + { + if (circe_memory_equal ( + identity, + peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity, + CIRCE_IDENTITY_LEN)) + { + *out = &(peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN]); + return true; + } + } + return false; +} + +static enum circe_result +unseal_payload (struct circe_peer *peer, struct circe_cbor_decoder *cd, + uint8_t *out, size_t *out_len) +{ + enum circe_cbor_major_type major_type; + uint64_t field; + uint64_t sequence_number; + + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_SEQ_NUMBER, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &sequence_number), + CIRCE_RESULT_INVALID_PACKET); + + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_PAYLOAD, + CIRCE_RESULT_INVALID_PACKET); + + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + struct circe_cbor_buffer sealed; + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &sealed), + CIRCE_RESULT_INVALID_PACKET); + // Make sure we got the full payload + CIRCE_CORE_ASSERT (sealed.buf_len + <= cd->buf_len - (cd->cursor - cd->buf_start), + CIRCE_RESULT_INVALID_PACKET); + uint8_t nonce[sizeof (uint64_t)]; + for (unsigned int i = 0; i < sizeof (uint64_t); ++i) + { + nonce[i] = (sequence_number >> (i * 8)) & 0xff; + } + if (!tct_aead_chacha20_poly1305_decrypt_and_verify ( + cd->buf_start, cd->cursor - cd->buf_start, peer->rx_key, nonce, + sealed.buf_start, sealed.buf_len, out)) + { + return CIRCE_RESULT_INVALID_MAC; + } + *out_len = sealed.buf_len - 16; // Don't include the MAC's length + return CIRCE_RESULT_SUCCESS; +} + +static bool +locations_equal (const struct circe_location a, const struct circe_location b) +{ + if (a.ip_type != b.ip_type) + { + return false; + } + else if (a.port != b.port) + { + return false; + } + else if (a.ip_type == CIRCE_ADDRESS_IPV4) + { + return circe_memory_equal (a.ip_addr.ipv4, b.ip_addr.ipv4, + sizeof (a.ip_addr.ipv4)); + } + else if (a.ip_type == CIRCE_ADDRESS_IPV6) + { + return circe_memory_equal (a.ip_addr.ipv6, b.ip_addr.ipv6, + sizeof (a.ip_addr.ipv6)); + } + // What? + return false; +} + enum circe_result -circe_handle_packet (struct circe_ctx *ctx, const uint8_t *in, uint32_t len, - uint64_t now_ms, uint8_t *out, uint32_t *out_len, - struct circe_event *events, uint32_t *n_events) +circe_handle_packet (const struct circe_location remote, const uint8_t *in, + size_t len, uint64_t now_ms, uint8_t *out, + size_t *out_len, struct circe_event *events, + size_t *n_events) { + *n_events = 0; + *out_len = 0; struct circe_cbor_decoder cd = { .buf_start = in, .buf_len = len,

@@ -31,7 +142,9 @@ CIRCE_RESULT_INVALID_PACKET);

uint64_t num_fields; CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &num_fields), CIRCE_RESULT_INVALID_PACKET); - CIRCE_CORE_ASSERT (num_fields > 0, CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (num_fields >= 2, CIRCE_RESULT_INVALID_PACKET); + + // Version CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED,

@@ -39,7 +152,102 @@ CIRCE_RESULT_INVALID_PACKET);

uint64_t field; CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &field), CIRCE_RESULT_INVALID_PACKET); - CIRCE_CORE_ASSERT (field < CIRCE_PACKET_FIELD_OVERMAX, + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_VERSION, + CIRCE_RESULT_INVALID_PACKET); + uint64_t version; + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &version), + CIRCE_RESULT_INVALID_PACKET); + // If it's anything but v1 we can't handle it (there is no v2) + CIRCE_CORE_ASSERT (field == CIRCE_VERSION_V1, CIRCE_RESULT_INVALID_PACKET); + + // Remote's identity + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_IDENTITY, + CIRCE_RESULT_INVALID_PACKET); + struct circe_cbor_buffer remote_id_buf; + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (&cd, &remote_id_buf), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (remote_id_buf.buf_len == CIRCE_IDENTITY_LEN, + CIRCE_RESULT_INVALID_PACKET); + struct circe_peer *remote_pt_entry; + bool peer_in_ram + = peer_table_lookup (remote_id_buf.buf_start, &remote_pt_entry); + + // Packet type + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, CIRCE_RESULT_INVALID_PACKET); - return CIRCE_RESULT_SUCCESS; + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_TYPE, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + uint64_t packet_type; + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &packet_type), + CIRCE_RESULT_INVALID_PACKET); + + if (packet_type != CIRCE_PACKET_ROUTE_CHALLENGE + && packet_type != CIRCE_PACKET_OPENTUNNEL && peer_in_ram) + { + // Verify that this came from our known remote location + bool good_location; + switch (remote_pt_entry->current_route) + { + case CIRCE_ROUTE_LAN0: + good_location = locations_equal (remote_pt_entry->lan0_loc, remote); + break; + case CIRCE_ROUTE_LAN1: + good_location = locations_equal (remote_pt_entry->lan1_loc, remote); + break; + case CIRCE_ROUTE_WAN: + good_location = locations_equal (remote_pt_entry->wan_loc, remote); + break; + case CIRCE_ROUTE_RELAY: + // What? + good_location = false; + break; + default: + return CIRCE_RESULT_INTERNAL_ERROR; + } + if (!good_location) + { + (*n_events)++; + events[0] = (struct circe_event){ + .event_type = CIRCE_EVENT_REJECTED_REMOTE, + }; + return CIRCE_RESULT_SUCCESS; + } + } + + switch (packet_type) + { + case CIRCE_PACKET_OPENTUNNEL: + case CIRCE_PACKET_ACKOPEN: + case CIRCE_PACKET_IDENTIFY: + case CIRCE_PACKET_DATA: + (*n_events)++; + events[0] = (struct circe_event){ + .event_type = CIRCE_EVENT_DATA_RECEIVED, + }; + return unseal_payload (remote_pt_entry, &cd, out, out_len); + case CIRCE_PACKET_COMMAND: + } + return CIRCE_RESULT_INVALID_PACKET; }
M core/core.hcore/core.h

@@ -1,6 +1,8 @@

#ifndef CIRCE_CORE_H #define CIRCE_CORE_H +#include "peer_table.h" +#include <stddef.h> #include <stdint.h> enum circe_result

@@ -8,29 +10,57 @@ {

CIRCE_RESULT_SUCCESS = 0, CIRCE_RESULT_INSUFFICIENT_MEM, CIRCE_RESULT_INVALID_PACKET, + CIRCE_RESULT_INVALID_MAC, + CIRCE_RESULT_INTERNAL_ERROR, }; -struct circe_ctx +enum circe_version { + CIRCE_VERSION_V1 = 0, }; struct circe_event { + enum + { + CIRCE_EVENT_DATA_RECEIVED, + CIRCE_EVENT_NEW_REMOTE, + CIRCE_EVENT_REJECTED_REMOTE, + } event_type; }; -/// Process a new raw Circe packet. +enum circe_deadline +{ + CIRCE_DEADLINE_NOW, + CIRCE_DEADLINE_INDEFINITE, + CIRCE_DEADLINE_AS_INDICATED, +}; + +/// Process a new incoming raw Circe packet. +enum circe_result circe_handle_packet (const struct circe_location remote, + const uint8_t *in, size_t len, + uint64_t now_ms, uint8_t *out, + size_t *out_len, + struct circe_event *events, + size_t *n_events); + +/// Put outgoing packet into Circe's internal logic. DOES NOT ACTUALLY SEND IT +/// OVER THE NETWORK; that is handled opaquely by the caller and `circe_tick`. enum circe_result -circe_handle_packet (struct circe_ctx *ctx, const uint8_t *in, uint32_t len, - uint64_t now_ms, uint8_t *out, uint32_t *out_len, - struct circe_event *events, uint32_t *n_events); +circe_send_outgoing_packet (const uint8_t remote_identity[CIRCE_IDENTITY_LEN], + const uint8_t *in, size_t len, uint64_t now_ms, + struct circe_event *events, size_t *n_events); -/// Get millisecond timestamp for next timer deadline (at which point -/// `circe_tick` should be called). -uint64_t circe_next_deadline (const struct circe_ctx *ctx); +/// Get deadline and corresponding millisecond timestamp (for +/// `CIRCE_DEADLINE_AS_INDICATED`) for next timer deadline, at which point +/// `circe_tick` should be called. Should always be called immediately after +/// any other core API function is called to stay up-to-date. +enum circe_deadline circe_next_deadline (uint64_t *ms_timestamp); /// Iterate the Circe core logic. Assumes `now_ms` increases monotonically with -/// time. -enum circe_result circe_tick (struct circe_ctx *ctx, uint64_t now_ms, - uint8_t *out, uint32_t *out_len); +/// time. If `out_len` is nonzero (do not pass in NULL), then `out` contains a +/// new raw Circe packet to be sent out to the location `destination`. +enum circe_result circe_tick (uint64_t now_ms, uint8_t *out, size_t *out_len, + struct circe_location *destination); #endif
M core/packet.hcore/packet.h

@@ -4,19 +4,22 @@

enum circe_packet_type { CIRCE_PACKET_OPENTUNNEL = 0x0, - CIRCE_PACKET_ACKOPEN = 0x1, - CIRCE_PACKET_IDENTIFY = 0x2, - CIRCE_PACKET_DATA = 0x3, - CIRCE_PACKET_COMMAND = 0x4, + CIRCE_PACKET_ACKOPEN, + CIRCE_PACKET_IDENTIFY, + CIRCE_PACKET_DATA, + CIRCE_PACKET_COMMAND, + CIRCE_PACKET_ROUTE_CHALLENGE, }; enum circe_packet_field { - CIRCE_PACKET_FIELD_TYPE = 0x0, + CIRCE_PACKET_FIELD_VERSION = 0x0, + CIRCE_PACKET_FIELD_IDENTITY, + CIRCE_PACKET_FIELD_TYPE, CIRCE_PACKET_FIELD_ECDH_FRAGMENT, CIRCE_PACKET_FIELD_MLKEM_PUBKEY, CIRCE_PACKET_FIELD_MLKEM_ENCAPSULATED, - CIRCE_PACKET_FIELD_IDENTITY, + CIRCE_PACKET_FIELD_SEQ_NUMBER, CIRCE_PACKET_FIELD_PAYLOAD, CIRCE_PACKET_FIELD_OVERMAX, };
M core/peer_table.hcore/peer_table.h

@@ -1,6 +1,50 @@

#ifndef CIRCE_CORE_PEER_TABLE_H #define CIRCE_CORE_PEER_TABLE_H +#include <stdbool.h> +#include <stdint.h> +struct circe_location +{ + enum + { + CIRCE_ADDRESS_IPV4, + CIRCE_ADDRESS_IPV6, + } ip_type; + uint16_t port; + union + { + uint8_t ipv4[4]; + uint8_t ipv6[16]; + } ip_addr; +}; + +#define CIRCE_PEER_TABLE_LEN 64 +#define CIRCE_IDENTITY_LEN 64 // SHA-512 hash of hostname || island name + +struct circe_peer +{ + bool double_lan; // lan1 is not valid if false + struct circe_location lan0_loc; + struct circe_location lan1_loc; + struct circe_location wan_loc; + uint8_t relay_server_identity[CIRCE_IDENTITY_LEN]; // SERVERS DON'T USE THIS, + // ONLY CLIENTS DO + uint8_t lan0_hash[32]; // SHA-256 + uint8_t lan1_hash[32]; + enum + { + CIRCE_ROUTE_LAN0, + CIRCE_ROUTE_LAN1, + CIRCE_ROUTE_WAN, + CIRCE_ROUTE_RELAY, + } current_route; + + uint8_t identity[CIRCE_IDENTITY_LEN]; + uint8_t mldsa_pubkey[1312]; // ML-DSA-44 + + uint8_t rx_key[32]; + uint8_t tx_key[32]; +}; #endif
A core/platform.h

@@ -0,0 +1,15 @@

+#ifndef CIRCE_CORE_PLATFORM_H +#define CIRCE_CORE_PLATFORM_H + +#include "peer_table.h" +#include <stdbool.h> +#include <stdint.h> + +void circe_platform_get_random_bytes (uint8_t *out, uint32_t len); + +void circe_platform_store_peer (const struct circe_peer *peer); + +bool circe_platform_recall_peer (uint8_t identity[CIRCE_IDENTITY_LEN], + struct circe_peer *out); + +#endif