Skip to content

Commit fece5eb

Browse files
committed
feat: implement replay protection and MAC computation for packet headers
1 parent ab1bd48 commit fece5eb

6 files changed

Lines changed: 304 additions & 15 deletions

File tree

core/protocol.c

Lines changed: 196 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include "protocol.h"
2+
#include "../include/vp_types.h"
23
#include <string.h>
4+
#include <stdlib.h>
35

46
// --------------------------------------
5-
// Helpers: fixed-endian (big endian) I/O
7+
// Helpers: fixed-endian I/O
68
// --------------------------------------
79
static void vp_write_u16_be(uint8_t *p, uint16_t v)
810
{
@@ -31,6 +33,164 @@ static uint32_t vp_read_u32_be(const uint8_t *p)
3133
((uint32_t)p[3]);
3234
}
3335

36+
static void vp_write_u64_be(uint8_t *p, uint64_t v)
37+
{
38+
p[0] = (uint8_t)((v >> 56) & 0xFF);
39+
p[1] = (uint8_t)((v >> 48) & 0xFF);
40+
p[2] = (uint8_t)((v >> 40) & 0xFF);
41+
p[3] = (uint8_t)((v >> 32) & 0xFF);
42+
p[4] = (uint8_t)((v >> 24) & 0xFF);
43+
p[5] = (uint8_t)((v >> 16) & 0xFF);
44+
p[6] = (uint8_t)((v >> 8) & 0xFF);
45+
p[7] = (uint8_t)(v & 0xFF);
46+
}
47+
48+
static uint64_t vp_read_u64_be(const uint8_t *p)
49+
{
50+
return (((uint64_t)p[0]) << 56) |
51+
(((uint64_t)p[1]) << 48) |
52+
(((uint64_t)p[2]) << 40) |
53+
(((uint64_t)p[3]) << 32) |
54+
(((uint64_t)p[4]) << 24) |
55+
(((uint64_t)p[5]) << 16) |
56+
(((uint64_t)p[6]) << 8) |
57+
((uint64_t)p[7]);
58+
}
59+
60+
// --------------------------------------
61+
// Simple keyed MAC (SipHash-2-4)
62+
// --------------------------------------
63+
static uint8_t g_vp_psk[16];
64+
static int g_vp_psk_loaded = 0;
65+
66+
static uint64_t vp_load_u64_le(const uint8_t *p)
67+
{
68+
return ((uint64_t)p[0]) |
69+
((uint64_t)p[1] << 8) |
70+
((uint64_t)p[2] << 16) |
71+
((uint64_t)p[3] << 24) |
72+
((uint64_t)p[4] << 32) |
73+
((uint64_t)p[5] << 40) |
74+
((uint64_t)p[6] << 48) |
75+
((uint64_t)p[7] << 56);
76+
}
77+
78+
static uint64_t vp_rotl64(uint64_t x, int b)
79+
{
80+
return (x << b) | (x >> (64 - b));
81+
}
82+
83+
static void vp_sipround(uint64_t *v0, uint64_t *v1,
84+
uint64_t *v2, uint64_t *v3)
85+
{
86+
*v0 += *v1;
87+
*v2 += *v3;
88+
*v1 = vp_rotl64(*v1, 13);
89+
*v3 = vp_rotl64(*v3, 16);
90+
*v1 ^= *v0;
91+
*v3 ^= *v2;
92+
*v0 = vp_rotl64(*v0, 32);
93+
*v2 += *v1;
94+
*v0 += *v3;
95+
*v1 = vp_rotl64(*v1, 17);
96+
*v3 = vp_rotl64(*v3, 21);
97+
*v1 ^= *v2;
98+
*v3 ^= *v0;
99+
*v2 = vp_rotl64(*v2, 32);
100+
}
101+
102+
static uint64_t vp_siphash24(const uint8_t key[16],
103+
const uint8_t *data,
104+
size_t len)
105+
{
106+
uint64_t k0 = vp_load_u64_le(key + 0);
107+
uint64_t k1 = vp_load_u64_le(key + 8);
108+
109+
uint64_t v0 = 0x736f6d6570736575ULL ^ k0;
110+
uint64_t v1 = 0x646f72616e646f6dULL ^ k1;
111+
uint64_t v2 = 0x6c7967656e657261ULL ^ k0;
112+
uint64_t v3 = 0x7465646279746573ULL ^ k1;
113+
114+
const uint8_t *end = data + len - (len % 8);
115+
const uint8_t *p = data;
116+
117+
while (p != end) {
118+
uint64_t m = vp_load_u64_le(p);
119+
v3 ^= m;
120+
vp_sipround(&v0, &v1, &v2, &v3);
121+
vp_sipround(&v0, &v1, &v2, &v3);
122+
v0 ^= m;
123+
p += 8;
124+
}
125+
126+
uint64_t b = (uint64_t)len << 56;
127+
size_t left = len & 7;
128+
switch (left) {
129+
case 7: b |= ((uint64_t)p[6] << 48);
130+
case 6: b |= ((uint64_t)p[5] << 40);
131+
case 5: b |= ((uint64_t)p[4] << 32);
132+
case 4: b |= ((uint64_t)p[3] << 24);
133+
case 3: b |= ((uint64_t)p[2] << 16);
134+
case 2: b |= ((uint64_t)p[1] << 8);
135+
case 1: b |= ((uint64_t)p[0]);
136+
default: break;
137+
}
138+
139+
v3 ^= b;
140+
vp_sipround(&v0, &v1, &v2, &v3);
141+
vp_sipround(&v0, &v1, &v2, &v3);
142+
v0 ^= b;
143+
144+
v2 ^= 0xff;
145+
vp_sipround(&v0, &v1, &v2, &v3);
146+
vp_sipround(&v0, &v1, &v2, &v3);
147+
vp_sipround(&v0, &v1, &v2, &v3);
148+
vp_sipround(&v0, &v1, &v2, &v3);
149+
150+
return v0 ^ v1 ^ v2 ^ v3;
151+
}
152+
153+
static void vp_auth_load_key(void)
154+
{
155+
if (g_vp_psk_loaded)
156+
return;
157+
158+
g_vp_psk_loaded = 1;
159+
160+
const char *env = getenv("VP_PSK");
161+
memset(g_vp_psk, 0, sizeof(g_vp_psk));
162+
163+
if (env && env[0]) {
164+
size_t n = strlen(env);
165+
if (n > sizeof(g_vp_psk))
166+
n = sizeof(g_vp_psk);
167+
memcpy(g_vp_psk, env, n);
168+
} else {
169+
// Default key (should be overridden in production via VP_PSK).
170+
const uint8_t default_key[16] = {
171+
0x56, 0x50, 0x4E, 0x32, // "VPN2"
172+
0x01, 0x23, 0x45, 0x67,
173+
0x89, 0xAB, 0xCD, 0xEF,
174+
0x10, 0x32, 0x54, 0x76
175+
};
176+
memcpy(g_vp_psk, default_key, sizeof(g_vp_psk));
177+
}
178+
}
179+
180+
static void vp_pack_header(uint8_t *buf, const vp_header_t *hdr, uint64_t auth_tag)
181+
{
182+
vp_write_u32_be(buf + 0, hdr->magic);
183+
buf[4] = hdr->version;
184+
buf[5] = hdr->type;
185+
vp_write_u16_be(buf + 6, (uint16_t)hdr->header_len);
186+
vp_write_u16_be(buf + 8, hdr->payload_len);
187+
vp_write_u16_be(buf + 10, hdr->flags);
188+
vp_write_u32_be(buf + 12, hdr->client_id);
189+
vp_write_u32_be(buf + 16, hdr->seq);
190+
vp_write_u32_be(buf + 20, hdr->checksum);
191+
vp_write_u64_be(buf + 24, auth_tag);
192+
}
193+
34194
// --------------------------------------
35195
// CRC32 IEEE (standard)
36196
// --------------------------------------
@@ -52,26 +212,30 @@ int vp_encode_packet(uint8_t *buf, size_t buf_len,
52212
const vp_header_t *hdr,
53213
const uint8_t *payload)
54214
{
215+
vp_auth_load_key();
216+
55217
size_t header_len = VP_HEADER_WIRE_LEN;
56218
size_t payload_len = hdr->payload_len;
57219
size_t total_len = header_len + payload_len;
58220
if (buf_len < total_len)
59221
return -1;
60222

61-
// Serialize header in a stable, padding-independent way
62-
vp_write_u32_be(buf + 0, hdr->magic);
63-
buf[4] = hdr->version;
64-
buf[5] = hdr->type;
65-
vp_write_u16_be(buf + 6, (uint16_t)header_len);
66-
vp_write_u16_be(buf + 8, hdr->payload_len);
67-
vp_write_u16_be(buf + 10, hdr->flags);
68-
vp_write_u32_be(buf + 12, hdr->client_id);
69-
vp_write_u32_be(buf + 16, hdr->seq);
70-
vp_write_u32_be(buf + 20, hdr->checksum);
223+
// Serialize header with auth_tag=0 for MAC computation
224+
vp_header_t tmp = *hdr;
225+
tmp.header_len = (uint16_t)header_len;
226+
tmp.auth_tag = 0;
227+
vp_pack_header(buf, &tmp, 0);
71228

72229
if (payload && payload_len > 0)
73230
memcpy(buf + header_len, payload, payload_len);
74231

232+
// Compute MAC over header (with auth_tag=0) + payload
233+
uint64_t tag = vp_siphash24(g_vp_psk, buf, total_len);
234+
235+
// Write final header including auth_tag
236+
tmp.auth_tag = tag;
237+
vp_pack_header(buf, &tmp, tag);
238+
75239
return (int)total_len;
76240
}
77241

@@ -81,6 +245,8 @@ int vp_encode_packet(uint8_t *buf, size_t buf_len,
81245
int vp_decode_header(const uint8_t *buf, size_t buf_len,
82246
vp_header_t *hdr)
83247
{
248+
vp_auth_load_key();
249+
84250
if (buf_len < VP_HEADER_WIRE_LEN)
85251
return -1;
86252

@@ -99,6 +265,7 @@ int vp_decode_header(const uint8_t *buf, size_t buf_len,
99265
hdr->client_id = vp_read_u32_be(buf + 12);
100266
hdr->seq = vp_read_u32_be(buf + 16);
101267
hdr->checksum = vp_read_u32_be(buf + 20);
268+
hdr->auth_tag = vp_read_u64_be(buf + 24);
102269

103270
// Basic bounds validation
104271
size_t header_len = hdr->header_len;
@@ -110,5 +277,23 @@ int vp_decode_header(const uint8_t *buf, size_t buf_len,
110277
if (buf_len < header_len + payload_len)
111278
return -4;
112279

280+
// Verify MAC over header (with auth_tag=0) + payload
281+
vp_header_t tmp = *hdr;
282+
tmp.auth_tag = 0;
283+
284+
uint8_t hdr_bytes[VP_HEADER_WIRE_LEN + VP_MAX_FRAME_LEN];
285+
if (header_len > VP_HEADER_WIRE_LEN)
286+
return -5;
287+
if (payload_len > VP_MAX_FRAME_LEN)
288+
return -6;
289+
290+
vp_pack_header(hdr_bytes, &tmp, 0);
291+
if (payload_len > 0)
292+
memcpy(hdr_bytes + header_len, buf + header_len, payload_len);
293+
294+
uint64_t expected = vp_siphash24(g_vp_psk, hdr_bytes, header_len + payload_len);
295+
if (expected != hdr->auth_tag)
296+
return -7;
297+
113298
return 0;
114299
}

core/protocol.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#include <stddef.h>
66

77
#define VP_MAGIC 0x56504E32 // "VPN2"
8-
#define VP_VERSION 2
9-
#define VP_HEADER_WIRE_LEN 24
8+
#define VP_VERSION 3
9+
#define VP_HEADER_WIRE_LEN 32
1010

1111
#pragma pack(push, 1)
1212
typedef struct {
@@ -19,6 +19,7 @@ typedef struct {
1919
uint32_t client_id;
2020
uint32_t seq; // anti-replay / reorder protection
2121
uint32_t checksum; // CRC32 payload
22+
uint64_t auth_tag; // MAC over header + payload
2223
} vp_header_t;
2324
#pragma pack(pop)
2425

core/switch_core.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ void vp_switch_update_client(uint32_t client_id,
164164
} else {
165165
e->in_use = 1;
166166
e->client_id = client_id;
167+
e->highest_seq = 0;
168+
e->replay_window = 0;
167169
}
168170

169171
e->addr = *addr;
@@ -198,6 +200,51 @@ int vp_switch_get_client_id_for_addr(const struct vp_os_addr *addr,
198200
return 0;
199201
}
200202

203+
int vp_switch_check_replay(uint32_t client_id, uint32_t seq)
204+
{
205+
if (client_id == 0 || client_id >= VP_CLIENT_MAX)
206+
return -1;
207+
208+
vp_client_entry_t *e = &client_table[client_id];
209+
if (!e->in_use)
210+
return -1;
211+
212+
// Sequence numbers start at 1; 0 is reserved for control.
213+
if (seq == 0)
214+
return -1;
215+
216+
uint32_t highest = e->highest_seq;
217+
218+
if (highest == 0) {
219+
e->highest_seq = seq;
220+
e->replay_window = 1ULL;
221+
return 0;
222+
}
223+
224+
if (seq > highest) {
225+
uint32_t delta = seq - highest;
226+
if (delta >= 64) {
227+
e->replay_window = 1ULL;
228+
} else {
229+
e->replay_window <<= delta;
230+
e->replay_window |= 1ULL;
231+
}
232+
e->highest_seq = seq;
233+
return 0;
234+
}
235+
236+
uint32_t diff = highest - seq;
237+
if (diff >= 64)
238+
return -1;
239+
240+
uint64_t mask = 1ULL << diff;
241+
if (e->replay_window & mask)
242+
return -1;
243+
244+
e->replay_window |= mask;
245+
return 0;
246+
}
247+
201248
void vp_switch_init(void)
202249
{
203250
memset(mac_table, 0, sizeof(mac_table));

core/switch_core.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ typedef struct {
1313
uint32_t client_id;
1414
struct vp_os_addr addr;
1515
uint64_t last_seen_ms;
16+
uint32_t highest_seq;
17+
uint64_t replay_window;
1618
} vp_client_entry_t;
1719

1820
void vp_switch_update_client(uint32_t client_id,
@@ -26,6 +28,10 @@ int vp_switch_get_client_addr(uint32_t client_id,
2628
int vp_switch_get_client_id_for_addr(const struct vp_os_addr *addr,
2729
uint32_t *out_client_id);
2830

31+
// Per-client replay protection (DATA / KEEPALIVE)
32+
// Returns 0 on accept, <0 on replay/too-old/invalid.
33+
int vp_switch_check_replay(uint32_t client_id, uint32_t seq);
34+
2935
// Callback used by switch_core to forward frames
3036
typedef void (*vp_forward_cb)(
3137
uint32_t src_client_id,

src/switchd.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,12 @@ int main(int argc, char **argv)
236236
continue;
237237
}
238238

239-
vp_switch_update_client(src_client_id, &src, now);
239+
if (vp_switch_check_replay(src_client_id, hdr.seq) < 0) {
240+
LOG_DEBUG("Drop DATA: replay or out-of-window (seq=%u)", hdr.seq);
241+
continue;
242+
}
240243

241-
int payload_len = hdr.payload_len;
244+
vp_switch_update_client(src_client_id, &src, now);
242245

243246
// invalid or overflow?
244247
if (payload_len < 0 || payload_len > VP_MAX_FRAME_LEN) {
@@ -264,6 +267,11 @@ int main(int argc, char **argv)
264267
if (vp_switch_get_client_id_for_addr(&src, &src_client_id) < 0)
265268
continue;
266269

270+
if (vp_switch_check_replay(src_client_id, hdr.seq) < 0) {
271+
LOG_DEBUG("Drop KEEPALIVE: replay or out-of-window (seq=%u)", hdr.seq);
272+
continue;
273+
}
274+
267275
vp_switch_update_client(src_client_id, &src, now);
268276
continue; // no frame forwarding
269277
}

0 commit comments

Comments
 (0)