From f75c2e70c0e494566dbbb20d408309412b4110e6 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Thu, 5 May 2022 10:49:46 +0200 Subject: [PATCH] initial commit Signed-off-by: Felix Fietkau --- .gitignore | 8 + CMakeLists.txt | 30 + curve25519-fiat32.h | 860 +++++++++++ curve25519-hacl64.h | 784 ++++++++++ curve25519.c | 98 ++ curve25519.h | 24 + examples/net0-ap1.key | 1 + examples/net0-ap2.key | 1 + examples/net0-master.key | 1 + examples/net0.json | 40 + examples/test-net0.sh | 11 + host.c | 289 ++++ host.h | 68 + main.c | 129 ++ network.c | 490 ++++++ network.h | 87 ++ pex.c | 520 +++++++ pex.h | 35 + scripts/json_pp.pm | 3147 ++++++++++++++++++++++++++++++++++++++ scripts/update-cmd.pl | 208 +++ service.c | 119 ++ service.h | 21 + siphash.c | 75 + siphash.h | 57 + ubus.c | 195 +++ unetd.h | 41 + utils.c | 140 ++ utils.h | 58 + wg-dummy.c | 118 ++ wg-linux.c | 312 ++++ wg-user.c | 481 ++++++ wg.c | 98 ++ wg.h | 62 + 33 files changed, 8608 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 curve25519-fiat32.h create mode 100644 curve25519-hacl64.h create mode 100644 curve25519.c create mode 100644 curve25519.h create mode 100644 examples/net0-ap1.key create mode 100644 examples/net0-ap2.key create mode 100644 examples/net0-master.key create mode 100644 examples/net0.json create mode 100755 examples/test-net0.sh create mode 100644 host.c create mode 100644 host.h create mode 100644 main.c create mode 100644 network.c create mode 100644 network.h create mode 100644 pex.c create mode 100644 pex.h create mode 100644 scripts/json_pp.pm create mode 100755 scripts/update-cmd.pl create mode 100644 service.c create mode 100644 service.h create mode 100644 siphash.c create mode 100644 siphash.h create mode 100644 ubus.c create mode 100644 unetd.h create mode 100644 utils.c create mode 100644 utils.h create mode 100644 wg-dummy.c create mode 100644 wg-linux.c create mode 100644 wg-user.c create mode 100644 wg.c create mode 100644 wg.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78d50c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +Makefile +CMakeCache.txt +CMakeFiles +*.cmake +*.a +*.so +*.dylib +unetd diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..50e1848 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.3) + +PROJECT(unetd C) + + +SET(SOURCES + main.c ubus.c network.c host.c service.c pex.c utils.c + curve25519.c siphash.c + wg.c wg-dummy.c wg-user.c +) + +SET(RUNSTATEDIR /var/run) + +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations -DRUNSTATEDIR="${RUNSTATEDIR}") +FIND_LIBRARY(libjson NAMES json-c json) + +IF(CMAKE_SYSTEM_NAME STREQUAL "Linux") + FIND_LIBRARY(nl nl-tiny) + SET(SOURCES ${SOURCES} wg-linux.c) +ELSE() + SET(nl "") +ENDIF() + +ADD_EXECUTABLE(unetd ${SOURCES}) +TARGET_LINK_LIBRARIES(unetd ubox ubus blobmsg_json ${libjson} ${nl}) + +INSTALL(TARGETS unetd + RUNTIME DESTINATION sbin + LIBRARY DESTINATION lib +) diff --git a/curve25519-fiat32.h b/curve25519-fiat32.h new file mode 100644 index 0000000..66f3309 --- /dev/null +++ b/curve25519-fiat32.h @@ -0,0 +1,860 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * Copyright (C) 2015-2016 The fiat-crypto Authors. + * Copyright (C) 2018-2020 Jason A. Donenfeld . All Rights Reserved. + * + * This is a machine-generated formally verified implementation of Curve25519 + * ECDH from: . Though originally + * machine generated, it has been tweaked to be suitable for use in the kernel. + * It is optimized for 32-bit machines and machines that cannot work efficiently + * with 128-bit integer types. + */ + +/* fe means field element. Here the field is \Z/(2^255-19). An element t, + * entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77 + * t[3]+2^102 t[4]+...+2^230 t[9]. + * fe limbs are bounded by 1.125*2^26,1.125*2^25,1.125*2^26,1.125*2^25,etc. + * Multiplication and carrying produce fe from fe_loose. + */ +typedef struct fe { u32 v[10]; } fe; + +/* fe_loose limbs are bounded by 3.375*2^26,3.375*2^25,3.375*2^26,3.375*2^25,etc + * Addition and subtraction produce fe_loose from (fe, fe). + */ +typedef struct fe_loose { u32 v[10]; } fe_loose; + +static __always_inline void fe_frombytes_impl(u32 h[10], const u8 *s) +{ + /* Ignores top bit of s. */ + u32 a0 = get_unaligned_le32(s); + u32 a1 = get_unaligned_le32(s+4); + u32 a2 = get_unaligned_le32(s+8); + u32 a3 = get_unaligned_le32(s+12); + u32 a4 = get_unaligned_le32(s+16); + u32 a5 = get_unaligned_le32(s+20); + u32 a6 = get_unaligned_le32(s+24); + u32 a7 = get_unaligned_le32(s+28); + h[0] = a0&((1<<26)-1); /* 26 used, 32-26 left. 26 */ + h[1] = (a0>>26) | ((a1&((1<<19)-1))<< 6); /* (32-26) + 19 = 6+19 = 25 */ + h[2] = (a1>>19) | ((a2&((1<<13)-1))<<13); /* (32-19) + 13 = 13+13 = 26 */ + h[3] = (a2>>13) | ((a3&((1<< 6)-1))<<19); /* (32-13) + 6 = 19+ 6 = 25 */ + h[4] = (a3>> 6); /* (32- 6) = 26 */ + h[5] = a4&((1<<25)-1); /* 25 */ + h[6] = (a4>>25) | ((a5&((1<<19)-1))<< 7); /* (32-25) + 19 = 7+19 = 26 */ + h[7] = (a5>>19) | ((a6&((1<<12)-1))<<13); /* (32-19) + 12 = 13+12 = 25 */ + h[8] = (a6>>12) | ((a7&((1<< 6)-1))<<20); /* (32-12) + 6 = 20+ 6 = 26 */ + h[9] = (a7>> 6)&((1<<25)-1); /* 25 */ +} + +static __always_inline void fe_frombytes(fe *h, const u8 *s) +{ + fe_frombytes_impl(h->v, s); +} + +static __always_inline u8 /*bool*/ +addcarryx_u25(u8 /*bool*/ c, u32 a, u32 b, u32 *low) +{ + /* This function extracts 25 bits of result and 1 bit of carry + * (26 total), so a 32-bit intermediate is sufficient. + */ + u32 x = a + b + c; + *low = x & ((1 << 25) - 1); + return (x >> 25) & 1; +} + +static __always_inline u8 /*bool*/ +addcarryx_u26(u8 /*bool*/ c, u32 a, u32 b, u32 *low) +{ + /* This function extracts 26 bits of result and 1 bit of carry + * (27 total), so a 32-bit intermediate is sufficient. + */ + u32 x = a + b + c; + *low = x & ((1 << 26) - 1); + return (x >> 26) & 1; +} + +static __always_inline u8 /*bool*/ +subborrow_u25(u8 /*bool*/ c, u32 a, u32 b, u32 *low) +{ + /* This function extracts 25 bits of result and 1 bit of borrow + * (26 total), so a 32-bit intermediate is sufficient. + */ + u32 x = a - b - c; + *low = x & ((1 << 25) - 1); + return x >> 31; +} + +static __always_inline u8 /*bool*/ +subborrow_u26(u8 /*bool*/ c, u32 a, u32 b, u32 *low) +{ + /* This function extracts 26 bits of result and 1 bit of borrow + *(27 total), so a 32-bit intermediate is sufficient. + */ + u32 x = a - b - c; + *low = x & ((1 << 26) - 1); + return x >> 31; +} + +static __always_inline u32 cmovznz32(u32 t, u32 z, u32 nz) +{ + t = -!!t; /* all set if nonzero, 0 if 0 */ + return (t&nz) | ((~t)&z); +} + +static __always_inline void fe_freeze(u32 out[10], const u32 in1[10]) +{ + { const u32 x17 = in1[9]; + { const u32 x18 = in1[8]; + { const u32 x16 = in1[7]; + { const u32 x14 = in1[6]; + { const u32 x12 = in1[5]; + { const u32 x10 = in1[4]; + { const u32 x8 = in1[3]; + { const u32 x6 = in1[2]; + { const u32 x4 = in1[1]; + { const u32 x2 = in1[0]; + { u32 x20; u8/*bool*/ x21 = subborrow_u26(0x0, x2, 0x3ffffed, &x20); + { u32 x23; u8/*bool*/ x24 = subborrow_u25(x21, x4, 0x1ffffff, &x23); + { u32 x26; u8/*bool*/ x27 = subborrow_u26(x24, x6, 0x3ffffff, &x26); + { u32 x29; u8/*bool*/ x30 = subborrow_u25(x27, x8, 0x1ffffff, &x29); + { u32 x32; u8/*bool*/ x33 = subborrow_u26(x30, x10, 0x3ffffff, &x32); + { u32 x35; u8/*bool*/ x36 = subborrow_u25(x33, x12, 0x1ffffff, &x35); + { u32 x38; u8/*bool*/ x39 = subborrow_u26(x36, x14, 0x3ffffff, &x38); + { u32 x41; u8/*bool*/ x42 = subborrow_u25(x39, x16, 0x1ffffff, &x41); + { u32 x44; u8/*bool*/ x45 = subborrow_u26(x42, x18, 0x3ffffff, &x44); + { u32 x47; u8/*bool*/ x48 = subborrow_u25(x45, x17, 0x1ffffff, &x47); + { u32 x49 = cmovznz32(x48, 0x0, 0xffffffff); + { u32 x50 = (x49 & 0x3ffffed); + { u32 x52; u8/*bool*/ x53 = addcarryx_u26(0x0, x20, x50, &x52); + { u32 x54 = (x49 & 0x1ffffff); + { u32 x56; u8/*bool*/ x57 = addcarryx_u25(x53, x23, x54, &x56); + { u32 x58 = (x49 & 0x3ffffff); + { u32 x60; u8/*bool*/ x61 = addcarryx_u26(x57, x26, x58, &x60); + { u32 x62 = (x49 & 0x1ffffff); + { u32 x64; u8/*bool*/ x65 = addcarryx_u25(x61, x29, x62, &x64); + { u32 x66 = (x49 & 0x3ffffff); + { u32 x68; u8/*bool*/ x69 = addcarryx_u26(x65, x32, x66, &x68); + { u32 x70 = (x49 & 0x1ffffff); + { u32 x72; u8/*bool*/ x73 = addcarryx_u25(x69, x35, x70, &x72); + { u32 x74 = (x49 & 0x3ffffff); + { u32 x76; u8/*bool*/ x77 = addcarryx_u26(x73, x38, x74, &x76); + { u32 x78 = (x49 & 0x1ffffff); + { u32 x80; u8/*bool*/ x81 = addcarryx_u25(x77, x41, x78, &x80); + { u32 x82 = (x49 & 0x3ffffff); + { u32 x84; u8/*bool*/ x85 = addcarryx_u26(x81, x44, x82, &x84); + { u32 x86 = (x49 & 0x1ffffff); + { u32 x88; addcarryx_u25(x85, x47, x86, &x88); + out[0] = x52; + out[1] = x56; + out[2] = x60; + out[3] = x64; + out[4] = x68; + out[5] = x72; + out[6] = x76; + out[7] = x80; + out[8] = x84; + out[9] = x88; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +} + +static __always_inline void fe_tobytes(u8 s[32], const fe *f) +{ + u32 h[10]; + fe_freeze(h, f->v); + s[0] = h[0] >> 0; + s[1] = h[0] >> 8; + s[2] = h[0] >> 16; + s[3] = (h[0] >> 24) | (h[1] << 2); + s[4] = h[1] >> 6; + s[5] = h[1] >> 14; + s[6] = (h[1] >> 22) | (h[2] << 3); + s[7] = h[2] >> 5; + s[8] = h[2] >> 13; + s[9] = (h[2] >> 21) | (h[3] << 5); + s[10] = h[3] >> 3; + s[11] = h[3] >> 11; + s[12] = (h[3] >> 19) | (h[4] << 6); + s[13] = h[4] >> 2; + s[14] = h[4] >> 10; + s[15] = h[4] >> 18; + s[16] = h[5] >> 0; + s[17] = h[5] >> 8; + s[18] = h[5] >> 16; + s[19] = (h[5] >> 24) | (h[6] << 1); + s[20] = h[6] >> 7; + s[21] = h[6] >> 15; + s[22] = (h[6] >> 23) | (h[7] << 3); + s[23] = h[7] >> 5; + s[24] = h[7] >> 13; + s[25] = (h[7] >> 21) | (h[8] << 4); + s[26] = h[8] >> 4; + s[27] = h[8] >> 12; + s[28] = (h[8] >> 20) | (h[9] << 6); + s[29] = h[9] >> 2; + s[30] = h[9] >> 10; + s[31] = h[9] >> 18; +} + +/* h = f */ +static __always_inline void fe_copy(fe *h, const fe *f) +{ + memmove(h, f, sizeof(u32) * 10); +} + +static __always_inline void fe_copy_lt(fe_loose *h, const fe *f) +{ + memmove(h, f, sizeof(u32) * 10); +} + +/* h = 0 */ +static __always_inline void fe_0(fe *h) +{ + memset(h, 0, sizeof(u32) * 10); +} + +/* h = 1 */ +static __always_inline void fe_1(fe *h) +{ + memset(h, 0, sizeof(u32) * 10); + h->v[0] = 1; +} + +static void fe_add_impl(u32 out[10], const u32 in1[10], const u32 in2[10]) +{ + { const u32 x20 = in1[9]; + { const u32 x21 = in1[8]; + { const u32 x19 = in1[7]; + { const u32 x17 = in1[6]; + { const u32 x15 = in1[5]; + { const u32 x13 = in1[4]; + { const u32 x11 = in1[3]; + { const u32 x9 = in1[2]; + { const u32 x7 = in1[1]; + { const u32 x5 = in1[0]; + { const u32 x38 = in2[9]; + { const u32 x39 = in2[8]; + { const u32 x37 = in2[7]; + { const u32 x35 = in2[6]; + { const u32 x33 = in2[5]; + { const u32 x31 = in2[4]; + { const u32 x29 = in2[3]; + { const u32 x27 = in2[2]; + { const u32 x25 = in2[1]; + { const u32 x23 = in2[0]; + out[0] = (x5 + x23); + out[1] = (x7 + x25); + out[2] = (x9 + x27); + out[3] = (x11 + x29); + out[4] = (x13 + x31); + out[5] = (x15 + x33); + out[6] = (x17 + x35); + out[7] = (x19 + x37); + out[8] = (x21 + x39); + out[9] = (x20 + x38); + }}}}}}}}}}}}}}}}}}}} +} + +/* h = f + g + * Can overlap h with f or g. + */ +static __always_inline void fe_add(fe_loose *h, const fe *f, const fe *g) +{ + fe_add_impl(h->v, f->v, g->v); +} + +static void fe_sub_impl(u32 out[10], const u32 in1[10], const u32 in2[10]) +{ + { const u32 x20 = in1[9]; + { const u32 x21 = in1[8]; + { const u32 x19 = in1[7]; + { const u32 x17 = in1[6]; + { const u32 x15 = in1[5]; + { const u32 x13 = in1[4]; + { const u32 x11 = in1[3]; + { const u32 x9 = in1[2]; + { const u32 x7 = in1[1]; + { const u32 x5 = in1[0]; + { const u32 x38 = in2[9]; + { const u32 x39 = in2[8]; + { const u32 x37 = in2[7]; + { const u32 x35 = in2[6]; + { const u32 x33 = in2[5]; + { const u32 x31 = in2[4]; + { const u32 x29 = in2[3]; + { const u32 x27 = in2[2]; + { const u32 x25 = in2[1]; + { const u32 x23 = in2[0]; + out[0] = ((0x7ffffda + x5) - x23); + out[1] = ((0x3fffffe + x7) - x25); + out[2] = ((0x7fffffe + x9) - x27); + out[3] = ((0x3fffffe + x11) - x29); + out[4] = ((0x7fffffe + x13) - x31); + out[5] = ((0x3fffffe + x15) - x33); + out[6] = ((0x7fffffe + x17) - x35); + out[7] = ((0x3fffffe + x19) - x37); + out[8] = ((0x7fffffe + x21) - x39); + out[9] = ((0x3fffffe + x20) - x38); + }}}}}}}}}}}}}}}}}}}} +} + +/* h = f - g + * Can overlap h with f or g. + */ +static __always_inline void fe_sub(fe_loose *h, const fe *f, const fe *g) +{ + fe_sub_impl(h->v, f->v, g->v); +} + +static void fe_mul_impl(u32 out[10], const u32 in1[10], const u32 in2[10]) +{ + { const u32 x20 = in1[9]; + { const u32 x21 = in1[8]; + { const u32 x19 = in1[7]; + { const u32 x17 = in1[6]; + { const u32 x15 = in1[5]; + { const u32 x13 = in1[4]; + { const u32 x11 = in1[3]; + { const u32 x9 = in1[2]; + { const u32 x7 = in1[1]; + { const u32 x5 = in1[0]; + { const u32 x38 = in2[9]; + { const u32 x39 = in2[8]; + { const u32 x37 = in2[7]; + { const u32 x35 = in2[6]; + { const u32 x33 = in2[5]; + { const u32 x31 = in2[4]; + { const u32 x29 = in2[3]; + { const u32 x27 = in2[2]; + { const u32 x25 = in2[1]; + { const u32 x23 = in2[0]; + { u64 x40 = ((u64)x23 * x5); + { u64 x41 = (((u64)x23 * x7) + ((u64)x25 * x5)); + { u64 x42 = ((((u64)(0x2 * x25) * x7) + ((u64)x23 * x9)) + ((u64)x27 * x5)); + { u64 x43 = (((((u64)x25 * x9) + ((u64)x27 * x7)) + ((u64)x23 * x11)) + ((u64)x29 * x5)); + { u64 x44 = (((((u64)x27 * x9) + (0x2 * (((u64)x25 * x11) + ((u64)x29 * x7)))) + ((u64)x23 * x13)) + ((u64)x31 * x5)); + { u64 x45 = (((((((u64)x27 * x11) + ((u64)x29 * x9)) + ((u64)x25 * x13)) + ((u64)x31 * x7)) + ((u64)x23 * x15)) + ((u64)x33 * x5)); + { u64 x46 = (((((0x2 * ((((u64)x29 * x11) + ((u64)x25 * x15)) + ((u64)x33 * x7))) + ((u64)x27 * x13)) + ((u64)x31 * x9)) + ((u64)x23 * x17)) + ((u64)x35 * x5)); + { u64 x47 = (((((((((u64)x29 * x13) + ((u64)x31 * x11)) + ((u64)x27 * x15)) + ((u64)x33 * x9)) + ((u64)x25 * x17)) + ((u64)x35 * x7)) + ((u64)x23 * x19)) + ((u64)x37 * x5)); + { u64 x48 = (((((((u64)x31 * x13) + (0x2 * (((((u64)x29 * x15) + ((u64)x33 * x11)) + ((u64)x25 * x19)) + ((u64)x37 * x7)))) + ((u64)x27 * x17)) + ((u64)x35 * x9)) + ((u64)x23 * x21)) + ((u64)x39 * x5)); + { u64 x49 = (((((((((((u64)x31 * x15) + ((u64)x33 * x13)) + ((u64)x29 * x17)) + ((u64)x35 * x11)) + ((u64)x27 * x19)) + ((u64)x37 * x9)) + ((u64)x25 * x21)) + ((u64)x39 * x7)) + ((u64)x23 * x20)) + ((u64)x38 * x5)); + { u64 x50 = (((((0x2 * ((((((u64)x33 * x15) + ((u64)x29 * x19)) + ((u64)x37 * x11)) + ((u64)x25 * x20)) + ((u64)x38 * x7))) + ((u64)x31 * x17)) + ((u64)x35 * x13)) + ((u64)x27 * x21)) + ((u64)x39 * x9)); + { u64 x51 = (((((((((u64)x33 * x17) + ((u64)x35 * x15)) + ((u64)x31 * x19)) + ((u64)x37 * x13)) + ((u64)x29 * x21)) + ((u64)x39 * x11)) + ((u64)x27 * x20)) + ((u64)x38 * x9)); + { u64 x52 = (((((u64)x35 * x17) + (0x2 * (((((u64)x33 * x19) + ((u64)x37 * x15)) + ((u64)x29 * x20)) + ((u64)x38 * x11)))) + ((u64)x31 * x21)) + ((u64)x39 * x13)); + { u64 x53 = (((((((u64)x35 * x19) + ((u64)x37 * x17)) + ((u64)x33 * x21)) + ((u64)x39 * x15)) + ((u64)x31 * x20)) + ((u64)x38 * x13)); + { u64 x54 = (((0x2 * ((((u64)x37 * x19) + ((u64)x33 * x20)) + ((u64)x38 * x15))) + ((u64)x35 * x21)) + ((u64)x39 * x17)); + { u64 x55 = (((((u64)x37 * x21) + ((u64)x39 * x19)) + ((u64)x35 * x20)) + ((u64)x38 * x17)); + { u64 x56 = (((u64)x39 * x21) + (0x2 * (((u64)x37 * x20) + ((u64)x38 * x19)))); + { u64 x57 = (((u64)x39 * x20) + ((u64)x38 * x21)); + { u64 x58 = ((u64)(0x2 * x38) * x20); + { u64 x59 = (x48 + (x58 << 0x4)); + { u64 x60 = (x59 + (x58 << 0x1)); + { u64 x61 = (x60 + x58); + { u64 x62 = (x47 + (x57 << 0x4)); + { u64 x63 = (x62 + (x57 << 0x1)); + { u64 x64 = (x63 + x57); + { u64 x65 = (x46 + (x56 << 0x4)); + { u64 x66 = (x65 + (x56 << 0x1)); + { u64 x67 = (x66 + x56); + { u64 x68 = (x45 + (x55 << 0x4)); + { u64 x69 = (x68 + (x55 << 0x1)); + { u64 x70 = (x69 + x55); + { u64 x71 = (x44 + (x54 << 0x4)); + { u64 x72 = (x71 + (x54 << 0x1)); + { u64 x73 = (x72 + x54); + { u64 x74 = (x43 + (x53 << 0x4)); + { u64 x75 = (x74 + (x53 << 0x1)); + { u64 x76 = (x75 + x53); + { u64 x77 = (x42 + (x52 << 0x4)); + { u64 x78 = (x77 + (x52 << 0x1)); + { u64 x79 = (x78 + x52); + { u64 x80 = (x41 + (x51 << 0x4)); + { u64 x81 = (x80 + (x51 << 0x1)); + { u64 x82 = (x81 + x51); + { u64 x83 = (x40 + (x50 << 0x4)); + { u64 x84 = (x83 + (x50 << 0x1)); + { u64 x85 = (x84 + x50); + { u64 x86 = (x85 >> 0x1a); + { u32 x87 = ((u32)x85 & 0x3ffffff); + { u64 x88 = (x86 + x82); + { u64 x89 = (x88 >> 0x19); + { u32 x90 = ((u32)x88 & 0x1ffffff); + { u64 x91 = (x89 + x79); + { u64 x92 = (x91 >> 0x1a); + { u32 x93 = ((u32)x91 & 0x3ffffff); + { u64 x94 = (x92 + x76); + { u64 x95 = (x94 >> 0x19); + { u32 x96 = ((u32)x94 & 0x1ffffff); + { u64 x97 = (x95 + x73); + { u64 x98 = (x97 >> 0x1a); + { u32 x99 = ((u32)x97 & 0x3ffffff); + { u64 x100 = (x98 + x70); + { u64 x101 = (x100 >> 0x19); + { u32 x102 = ((u32)x100 & 0x1ffffff); + { u64 x103 = (x101 + x67); + { u64 x104 = (x103 >> 0x1a); + { u32 x105 = ((u32)x103 & 0x3ffffff); + { u64 x106 = (x104 + x64); + { u64 x107 = (x106 >> 0x19); + { u32 x108 = ((u32)x106 & 0x1ffffff); + { u64 x109 = (x107 + x61); + { u64 x110 = (x109 >> 0x1a); + { u32 x111 = ((u32)x109 & 0x3ffffff); + { u64 x112 = (x110 + x49); + { u64 x113 = (x112 >> 0x19); + { u32 x114 = ((u32)x112 & 0x1ffffff); + { u64 x115 = (x87 + (0x13 * x113)); + { u32 x116 = (u32) (x115 >> 0x1a); + { u32 x117 = ((u32)x115 & 0x3ffffff); + { u32 x118 = (x116 + x90); + { u32 x119 = (x118 >> 0x19); + { u32 x120 = (x118 & 0x1ffffff); + out[0] = x117; + out[1] = x120; + out[2] = (x119 + x93); + out[3] = x96; + out[4] = x99; + out[5] = x102; + out[6] = x105; + out[7] = x108; + out[8] = x111; + out[9] = x114; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +} + +static __always_inline void fe_mul_ttt(fe *h, const fe *f, const fe *g) +{ + fe_mul_impl(h->v, f->v, g->v); +} + +static __always_inline void fe_mul_tlt(fe *h, const fe_loose *f, const fe *g) +{ + fe_mul_impl(h->v, f->v, g->v); +} + +static __always_inline void +fe_mul_tll(fe *h, const fe_loose *f, const fe_loose *g) +{ + fe_mul_impl(h->v, f->v, g->v); +} + +static void fe_sqr_impl(u32 out[10], const u32 in1[10]) +{ + { const u32 x17 = in1[9]; + { const u32 x18 = in1[8]; + { const u32 x16 = in1[7]; + { const u32 x14 = in1[6]; + { const u32 x12 = in1[5]; + { const u32 x10 = in1[4]; + { const u32 x8 = in1[3]; + { const u32 x6 = in1[2]; + { const u32 x4 = in1[1]; + { const u32 x2 = in1[0]; + { u64 x19 = ((u64)x2 * x2); + { u64 x20 = ((u64)(0x2 * x2) * x4); + { u64 x21 = (0x2 * (((u64)x4 * x4) + ((u64)x2 * x6))); + { u64 x22 = (0x2 * (((u64)x4 * x6) + ((u64)x2 * x8))); + { u64 x23 = ((((u64)x6 * x6) + ((u64)(0x4 * x4) * x8)) + ((u64)(0x2 * x2) * x10)); + { u64 x24 = (0x2 * ((((u64)x6 * x8) + ((u64)x4 * x10)) + ((u64)x2 * x12))); + { u64 x25 = (0x2 * (((((u64)x8 * x8) + ((u64)x6 * x10)) + ((u64)x2 * x14)) + ((u64)(0x2 * x4) * x12))); + { u64 x26 = (0x2 * (((((u64)x8 * x10) + ((u64)x6 * x12)) + ((u64)x4 * x14)) + ((u64)x2 * x16))); + { u64 x27 = (((u64)x10 * x10) + (0x2 * ((((u64)x6 * x14) + ((u64)x2 * x18)) + (0x2 * (((u64)x4 * x16) + ((u64)x8 * x12)))))); + { u64 x28 = (0x2 * ((((((u64)x10 * x12) + ((u64)x8 * x14)) + ((u64)x6 * x16)) + ((u64)x4 * x18)) + ((u64)x2 * x17))); + { u64 x29 = (0x2 * (((((u64)x12 * x12) + ((u64)x10 * x14)) + ((u64)x6 * x18)) + (0x2 * (((u64)x8 * x16) + ((u64)x4 * x17))))); + { u64 x30 = (0x2 * (((((u64)x12 * x14) + ((u64)x10 * x16)) + ((u64)x8 * x18)) + ((u64)x6 * x17))); + { u64 x31 = (((u64)x14 * x14) + (0x2 * (((u64)x10 * x18) + (0x2 * (((u64)x12 * x16) + ((u64)x8 * x17)))))); + { u64 x32 = (0x2 * ((((u64)x14 * x16) + ((u64)x12 * x18)) + ((u64)x10 * x17))); + { u64 x33 = (0x2 * ((((u64)x16 * x16) + ((u64)x14 * x18)) + ((u64)(0x2 * x12) * x17))); + { u64 x34 = (0x2 * (((u64)x16 * x18) + ((u64)x14 * x17))); + { u64 x35 = (((u64)x18 * x18) + ((u64)(0x4 * x16) * x17)); + { u64 x36 = ((u64)(0x2 * x18) * x17); + { u64 x37 = ((u64)(0x2 * x17) * x17); + { u64 x38 = (x27 + (x37 << 0x4)); + { u64 x39 = (x38 + (x37 << 0x1)); + { u64 x40 = (x39 + x37); + { u64 x41 = (x26 + (x36 << 0x4)); + { u64 x42 = (x41 + (x36 << 0x1)); + { u64 x43 = (x42 + x36); + { u64 x44 = (x25 + (x35 << 0x4)); + { u64 x45 = (x44 + (x35 << 0x1)); + { u64 x46 = (x45 + x35); + { u64 x47 = (x24 + (x34 << 0x4)); + { u64 x48 = (x47 + (x34 << 0x1)); + { u64 x49 = (x48 + x34); + { u64 x50 = (x23 + (x33 << 0x4)); + { u64 x51 = (x50 + (x33 << 0x1)); + { u64 x52 = (x51 + x33); + { u64 x53 = (x22 + (x32 << 0x4)); + { u64 x54 = (x53 + (x32 << 0x1)); + { u64 x55 = (x54 + x32); + { u64 x56 = (x21 + (x31 << 0x4)); + { u64 x57 = (x56 + (x31 << 0x1)); + { u64 x58 = (x57 + x31); + { u64 x59 = (x20 + (x30 << 0x4)); + { u64 x60 = (x59 + (x30 << 0x1)); + { u64 x61 = (x60 + x30); + { u64 x62 = (x19 + (x29 << 0x4)); + { u64 x63 = (x62 + (x29 << 0x1)); + { u64 x64 = (x63 + x29); + { u64 x65 = (x64 >> 0x1a); + { u32 x66 = ((u32)x64 & 0x3ffffff); + { u64 x67 = (x65 + x61); + { u64 x68 = (x67 >> 0x19); + { u32 x69 = ((u32)x67 & 0x1ffffff); + { u64 x70 = (x68 + x58); + { u64 x71 = (x70 >> 0x1a); + { u32 x72 = ((u32)x70 & 0x3ffffff); + { u64 x73 = (x71 + x55); + { u64 x74 = (x73 >> 0x19); + { u32 x75 = ((u32)x73 & 0x1ffffff); + { u64 x76 = (x74 + x52); + { u64 x77 = (x76 >> 0x1a); + { u32 x78 = ((u32)x76 & 0x3ffffff); + { u64 x79 = (x77 + x49); + { u64 x80 = (x79 >> 0x19); + { u32 x81 = ((u32)x79 & 0x1ffffff); + { u64 x82 = (x80 + x46); + { u64 x83 = (x82 >> 0x1a); + { u32 x84 = ((u32)x82 & 0x3ffffff); + { u64 x85 = (x83 + x43); + { u64 x86 = (x85 >> 0x19); + { u32 x87 = ((u32)x85 & 0x1ffffff); + { u64 x88 = (x86 + x40); + { u64 x89 = (x88 >> 0x1a); + { u32 x90 = ((u32)x88 & 0x3ffffff); + { u64 x91 = (x89 + x28); + { u64 x92 = (x91 >> 0x19); + { u32 x93 = ((u32)x91 & 0x1ffffff); + { u64 x94 = (x66 + (0x13 * x92)); + { u32 x95 = (u32) (x94 >> 0x1a); + { u32 x96 = ((u32)x94 & 0x3ffffff); + { u32 x97 = (x95 + x69); + { u32 x98 = (x97 >> 0x19); + { u32 x99 = (x97 & 0x1ffffff); + out[0] = x96; + out[1] = x99; + out[2] = (x98 + x72); + out[3] = x75; + out[4] = x78; + out[5] = x81; + out[6] = x84; + out[7] = x87; + out[8] = x90; + out[9] = x93; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +} + +static __always_inline void fe_sq_tl(fe *h, const fe_loose *f) +{ + fe_sqr_impl(h->v, f->v); +} + +static __always_inline void fe_sq_tt(fe *h, const fe *f) +{ + fe_sqr_impl(h->v, f->v); +} + +static __always_inline void fe_loose_invert(fe *out, const fe_loose *z) +{ + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq_tl(&t0, z); + fe_sq_tt(&t1, &t0); + for (i = 1; i < 2; ++i) + fe_sq_tt(&t1, &t1); + fe_mul_tlt(&t1, z, &t1); + fe_mul_ttt(&t0, &t0, &t1); + fe_sq_tt(&t2, &t0); + fe_mul_ttt(&t1, &t1, &t2); + fe_sq_tt(&t2, &t1); + for (i = 1; i < 5; ++i) + fe_sq_tt(&t2, &t2); + fe_mul_ttt(&t1, &t2, &t1); + fe_sq_tt(&t2, &t1); + for (i = 1; i < 10; ++i) + fe_sq_tt(&t2, &t2); + fe_mul_ttt(&t2, &t2, &t1); + fe_sq_tt(&t3, &t2); + for (i = 1; i < 20; ++i) + fe_sq_tt(&t3, &t3); + fe_mul_ttt(&t2, &t3, &t2); + fe_sq_tt(&t2, &t2); + for (i = 1; i < 10; ++i) + fe_sq_tt(&t2, &t2); + fe_mul_ttt(&t1, &t2, &t1); + fe_sq_tt(&t2, &t1); + for (i = 1; i < 50; ++i) + fe_sq_tt(&t2, &t2); + fe_mul_ttt(&t2, &t2, &t1); + fe_sq_tt(&t3, &t2); + for (i = 1; i < 100; ++i) + fe_sq_tt(&t3, &t3); + fe_mul_ttt(&t2, &t3, &t2); + fe_sq_tt(&t2, &t2); + for (i = 1; i < 50; ++i) + fe_sq_tt(&t2, &t2); + fe_mul_ttt(&t1, &t2, &t1); + fe_sq_tt(&t1, &t1); + for (i = 1; i < 5; ++i) + fe_sq_tt(&t1, &t1); + fe_mul_ttt(out, &t1, &t0); +} + +static __always_inline void fe_invert(fe *out, const fe *z) +{ + fe_loose l; + fe_copy_lt(&l, z); + fe_loose_invert(out, &l); +} + +/* Replace (f,g) with (g,f) if b == 1; + * replace (f,g) with (f,g) if b == 0. + * + * Preconditions: b in {0,1} + */ +static __always_inline void fe_cswap(fe *f, fe *g, unsigned int b) +{ + unsigned i; + b = 0 - b; + for (i = 0; i < 10; i++) { + u32 x = f->v[i] ^ g->v[i]; + x &= b; + f->v[i] ^= x; + g->v[i] ^= x; + } +} + +/* NOTE: based on fiat-crypto fe_mul, edited for in2=121666, 0, 0.*/ +static __always_inline void fe_mul_121666_impl(u32 out[10], const u32 in1[10]) +{ + { const u32 x20 = in1[9]; + { const u32 x21 = in1[8]; + { const u32 x19 = in1[7]; + { const u32 x17 = in1[6]; + { const u32 x15 = in1[5]; + { const u32 x13 = in1[4]; + { const u32 x11 = in1[3]; + { const u32 x9 = in1[2]; + { const u32 x7 = in1[1]; + { const u32 x5 = in1[0]; + { const u32 x38 = 0; + { const u32 x39 = 0; + { const u32 x37 = 0; + { const u32 x35 = 0; + { const u32 x33 = 0; + { const u32 x31 = 0; + { const u32 x29 = 0; + { const u32 x27 = 0; + { const u32 x25 = 0; + { const u32 x23 = 121666; + { u64 x40 = ((u64)x23 * x5); + { u64 x41 = (((u64)x23 * x7) + ((u64)x25 * x5)); + { u64 x42 = ((((u64)(0x2 * x25) * x7) + ((u64)x23 * x9)) + ((u64)x27 * x5)); + { u64 x43 = (((((u64)x25 * x9) + ((u64)x27 * x7)) + ((u64)x23 * x11)) + ((u64)x29 * x5)); + { u64 x44 = (((((u64)x27 * x9) + (0x2 * (((u64)x25 * x11) + ((u64)x29 * x7)))) + ((u64)x23 * x13)) + ((u64)x31 * x5)); + { u64 x45 = (((((((u64)x27 * x11) + ((u64)x29 * x9)) + ((u64)x25 * x13)) + ((u64)x31 * x7)) + ((u64)x23 * x15)) + ((u64)x33 * x5)); + { u64 x46 = (((((0x2 * ((((u64)x29 * x11) + ((u64)x25 * x15)) + ((u64)x33 * x7))) + ((u64)x27 * x13)) + ((u64)x31 * x9)) + ((u64)x23 * x17)) + ((u64)x35 * x5)); + { u64 x47 = (((((((((u64)x29 * x13) + ((u64)x31 * x11)) + ((u64)x27 * x15)) + ((u64)x33 * x9)) + ((u64)x25 * x17)) + ((u64)x35 * x7)) + ((u64)x23 * x19)) + ((u64)x37 * x5)); + { u64 x48 = (((((((u64)x31 * x13) + (0x2 * (((((u64)x29 * x15) + ((u64)x33 * x11)) + ((u64)x25 * x19)) + ((u64)x37 * x7)))) + ((u64)x27 * x17)) + ((u64)x35 * x9)) + ((u64)x23 * x21)) + ((u64)x39 * x5)); + { u64 x49 = (((((((((((u64)x31 * x15) + ((u64)x33 * x13)) + ((u64)x29 * x17)) + ((u64)x35 * x11)) + ((u64)x27 * x19)) + ((u64)x37 * x9)) + ((u64)x25 * x21)) + ((u64)x39 * x7)) + ((u64)x23 * x20)) + ((u64)x38 * x5)); + { u64 x50 = (((((0x2 * ((((((u64)x33 * x15) + ((u64)x29 * x19)) + ((u64)x37 * x11)) + ((u64)x25 * x20)) + ((u64)x38 * x7))) + ((u64)x31 * x17)) + ((u64)x35 * x13)) + ((u64)x27 * x21)) + ((u64)x39 * x9)); + { u64 x51 = (((((((((u64)x33 * x17) + ((u64)x35 * x15)) + ((u64)x31 * x19)) + ((u64)x37 * x13)) + ((u64)x29 * x21)) + ((u64)x39 * x11)) + ((u64)x27 * x20)) + ((u64)x38 * x9)); + { u64 x52 = (((((u64)x35 * x17) + (0x2 * (((((u64)x33 * x19) + ((u64)x37 * x15)) + ((u64)x29 * x20)) + ((u64)x38 * x11)))) + ((u64)x31 * x21)) + ((u64)x39 * x13)); + { u64 x53 = (((((((u64)x35 * x19) + ((u64)x37 * x17)) + ((u64)x33 * x21)) + ((u64)x39 * x15)) + ((u64)x31 * x20)) + ((u64)x38 * x13)); + { u64 x54 = (((0x2 * ((((u64)x37 * x19) + ((u64)x33 * x20)) + ((u64)x38 * x15))) + ((u64)x35 * x21)) + ((u64)x39 * x17)); + { u64 x55 = (((((u64)x37 * x21) + ((u64)x39 * x19)) + ((u64)x35 * x20)) + ((u64)x38 * x17)); + { u64 x56 = (((u64)x39 * x21) + (0x2 * (((u64)x37 * x20) + ((u64)x38 * x19)))); + { u64 x57 = (((u64)x39 * x20) + ((u64)x38 * x21)); + { u64 x58 = ((u64)(0x2 * x38) * x20); + { u64 x59 = (x48 + (x58 << 0x4)); + { u64 x60 = (x59 + (x58 << 0x1)); + { u64 x61 = (x60 + x58); + { u64 x62 = (x47 + (x57 << 0x4)); + { u64 x63 = (x62 + (x57 << 0x1)); + { u64 x64 = (x63 + x57); + { u64 x65 = (x46 + (x56 << 0x4)); + { u64 x66 = (x65 + (x56 << 0x1)); + { u64 x67 = (x66 + x56); + { u64 x68 = (x45 + (x55 << 0x4)); + { u64 x69 = (x68 + (x55 << 0x1)); + { u64 x70 = (x69 + x55); + { u64 x71 = (x44 + (x54 << 0x4)); + { u64 x72 = (x71 + (x54 << 0x1)); + { u64 x73 = (x72 + x54); + { u64 x74 = (x43 + (x53 << 0x4)); + { u64 x75 = (x74 + (x53 << 0x1)); + { u64 x76 = (x75 + x53); + { u64 x77 = (x42 + (x52 << 0x4)); + { u64 x78 = (x77 + (x52 << 0x1)); + { u64 x79 = (x78 + x52); + { u64 x80 = (x41 + (x51 << 0x4)); + { u64 x81 = (x80 + (x51 << 0x1)); + { u64 x82 = (x81 + x51); + { u64 x83 = (x40 + (x50 << 0x4)); + { u64 x84 = (x83 + (x50 << 0x1)); + { u64 x85 = (x84 + x50); + { u64 x86 = (x85 >> 0x1a); + { u32 x87 = ((u32)x85 & 0x3ffffff); + { u64 x88 = (x86 + x82); + { u64 x89 = (x88 >> 0x19); + { u32 x90 = ((u32)x88 & 0x1ffffff); + { u64 x91 = (x89 + x79); + { u64 x92 = (x91 >> 0x1a); + { u32 x93 = ((u32)x91 & 0x3ffffff); + { u64 x94 = (x92 + x76); + { u64 x95 = (x94 >> 0x19); + { u32 x96 = ((u32)x94 & 0x1ffffff); + { u64 x97 = (x95 + x73); + { u64 x98 = (x97 >> 0x1a); + { u32 x99 = ((u32)x97 & 0x3ffffff); + { u64 x100 = (x98 + x70); + { u64 x101 = (x100 >> 0x19); + { u32 x102 = ((u32)x100 & 0x1ffffff); + { u64 x103 = (x101 + x67); + { u64 x104 = (x103 >> 0x1a); + { u32 x105 = ((u32)x103 & 0x3ffffff); + { u64 x106 = (x104 + x64); + { u64 x107 = (x106 >> 0x19); + { u32 x108 = ((u32)x106 & 0x1ffffff); + { u64 x109 = (x107 + x61); + { u64 x110 = (x109 >> 0x1a); + { u32 x111 = ((u32)x109 & 0x3ffffff); + { u64 x112 = (x110 + x49); + { u64 x113 = (x112 >> 0x19); + { u32 x114 = ((u32)x112 & 0x1ffffff); + { u64 x115 = (x87 + (0x13 * x113)); + { u32 x116 = (u32) (x115 >> 0x1a); + { u32 x117 = ((u32)x115 & 0x3ffffff); + { u32 x118 = (x116 + x90); + { u32 x119 = (x118 >> 0x19); + { u32 x120 = (x118 & 0x1ffffff); + out[0] = x117; + out[1] = x120; + out[2] = (x119 + x93); + out[3] = x96; + out[4] = x99; + out[5] = x102; + out[6] = x105; + out[7] = x108; + out[8] = x111; + out[9] = x114; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +} + +static __always_inline void fe_mul121666(fe *h, const fe_loose *f) +{ + fe_mul_121666_impl(h->v, f->v); +} + +static void curve25519_generic(u8 out[CURVE25519_KEY_SIZE], + const u8 scalar[CURVE25519_KEY_SIZE], + const u8 point[CURVE25519_KEY_SIZE]) +{ + fe x1, x2, z2, x3, z3; + fe_loose x2l, z2l, x3l; + unsigned swap = 0; + int pos; + u8 e[32]; + + memcpy(e, scalar, 32); + curve25519_clamp_secret(e); + + /* The following implementation was transcribed to Coq and proven to + * correspond to unary scalar multiplication in affine coordinates given + * that x1 != 0 is the x coordinate of some point on the curve. It was + * also checked in Coq that doing a ladderstep with x1 = x3 = 0 gives + * z2' = z3' = 0, and z2 = z3 = 0 gives z2' = z3' = 0. The statement was + * quantified over the underlying field, so it applies to Curve25519 + * itself and the quadratic twist of Curve25519. It was not proven in + * Coq that prime-field arithmetic correctly simulates extension-field + * arithmetic on prime-field values. The decoding of the byte array + * representation of e was not considered. + * + * Specification of Montgomery curves in affine coordinates: + * + * + * Proof that these form a group that is isomorphic to a Weierstrass + * curve: + * + * + * Coq transcription and correctness proof of the loop + * (where scalarbits=255): + * + * + * preconditions: 0 <= e < 2^255 (not necessarily e < order), + * fe_invert(0) = 0 + */ + fe_frombytes(&x1, point); + fe_1(&x2); + fe_0(&z2); + fe_copy(&x3, &x1); + fe_1(&z3); + + for (pos = 254; pos >= 0; --pos) { + fe tmp0, tmp1; + fe_loose tmp0l, tmp1l; + /* loop invariant as of right before the test, for the case + * where x1 != 0: + * pos >= -1; if z2 = 0 then x2 is nonzero; if z3 = 0 then x3 + * is nonzero + * let r := e >> (pos+1) in the following equalities of + * projective points: + * to_xz (r*P) === if swap then (x3, z3) else (x2, z2) + * to_xz ((r+1)*P) === if swap then (x2, z2) else (x3, z3) + * x1 is the nonzero x coordinate of the nonzero + * point (r*P-(r+1)*P) + */ + unsigned b = 1 & (e[pos / 8] >> (pos & 7)); + swap ^= b; + fe_cswap(&x2, &x3, swap); + fe_cswap(&z2, &z3, swap); + swap = b; + /* Coq transcription of ladderstep formula (called from + * transcribed loop): + * + * + * x1 != 0 + * x1 = 0 + */ + fe_sub(&tmp0l, &x3, &z3); + fe_sub(&tmp1l, &x2, &z2); + fe_add(&x2l, &x2, &z2); + fe_add(&z2l, &x3, &z3); + fe_mul_tll(&z3, &tmp0l, &x2l); + fe_mul_tll(&z2, &z2l, &tmp1l); + fe_sq_tl(&tmp0, &tmp1l); + fe_sq_tl(&tmp1, &x2l); + fe_add(&x3l, &z3, &z2); + fe_sub(&z2l, &z3, &z2); + fe_mul_ttt(&x2, &tmp1, &tmp0); + fe_sub(&tmp1l, &tmp1, &tmp0); + fe_sq_tl(&z2, &z2l); + fe_mul121666(&z3, &tmp1l); + fe_sq_tl(&x3, &x3l); + fe_add(&tmp0l, &tmp0, &z3); + fe_mul_ttt(&z3, &x1, &z2); + fe_mul_tll(&z2, &tmp1l, &tmp0l); + } + /* here pos=-1, so r=e, so to_xz (e*P) === if swap then (x3, z3) + * else (x2, z2) + */ + fe_cswap(&x2, &x3, swap); + fe_cswap(&z2, &z3, swap); + + fe_invert(&z2, &z2); + fe_mul_ttt(&x2, &x2, &z2); + fe_tobytes(out, &x2); + + memzero_explicit(&x1, sizeof(x1)); + memzero_explicit(&x2, sizeof(x2)); + memzero_explicit(&z2, sizeof(z2)); + memzero_explicit(&x3, sizeof(x3)); + memzero_explicit(&z3, sizeof(z3)); + memzero_explicit(&x2l, sizeof(x2l)); + memzero_explicit(&z2l, sizeof(z2l)); + memzero_explicit(&x3l, sizeof(x3l)); + memzero_explicit(&e, sizeof(e)); +} diff --git a/curve25519-hacl64.h b/curve25519-hacl64.h new file mode 100644 index 0000000..1fba1f5 --- /dev/null +++ b/curve25519-hacl64.h @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * Copyright (C) 2016-2017 INRIA and Microsoft Corporation. + * Copyright (C) 2018-2020 Jason A. Donenfeld . All Rights Reserved. + * + * This is a machine-generated formally verified implementation of Curve25519 + * ECDH from: . Though originally machine + * generated, it has been tweaked to be suitable for use in the kernel. It is + * optimized for 64-bit machines that can efficiently work with 128-bit + * integer types. + */ + +typedef __uint128_t u128; + +static __always_inline u64 u64_eq_mask(u64 a, u64 b) +{ + u64 x = a ^ b; + u64 minus_x = ~x + (u64)1U; + u64 x_or_minus_x = x | minus_x; + u64 xnx = x_or_minus_x >> (u32)63U; + u64 c = xnx - (u64)1U; + return c; +} + +static __always_inline u64 u64_gte_mask(u64 a, u64 b) +{ + u64 x = a; + u64 y = b; + u64 x_xor_y = x ^ y; + u64 x_sub_y = x - y; + u64 x_sub_y_xor_y = x_sub_y ^ y; + u64 q = x_xor_y | x_sub_y_xor_y; + u64 x_xor_q = x ^ q; + u64 x_xor_q_ = x_xor_q >> (u32)63U; + u64 c = x_xor_q_ - (u64)1U; + return c; +} + +static __always_inline void modulo_carry_top(u64 *b) +{ + u64 b4 = b[4]; + u64 b0 = b[0]; + u64 b4_ = b4 & 0x7ffffffffffffLLU; + u64 b0_ = b0 + 19 * (b4 >> 51); + b[4] = b4_; + b[0] = b0_; +} + +static __always_inline void fproduct_copy_from_wide_(u64 *output, u128 *input) +{ + { + u128 xi = input[0]; + output[0] = ((u64)(xi)); + } + { + u128 xi = input[1]; + output[1] = ((u64)(xi)); + } + { + u128 xi = input[2]; + output[2] = ((u64)(xi)); + } + { + u128 xi = input[3]; + output[3] = ((u64)(xi)); + } + { + u128 xi = input[4]; + output[4] = ((u64)(xi)); + } +} + +static __always_inline void +fproduct_sum_scalar_multiplication_(u128 *output, u64 *input, u64 s) +{ + output[0] += (u128)input[0] * s; + output[1] += (u128)input[1] * s; + output[2] += (u128)input[2] * s; + output[3] += (u128)input[3] * s; + output[4] += (u128)input[4] * s; +} + +static __always_inline void fproduct_carry_wide_(u128 *tmp) +{ + { + u32 ctr = 0; + u128 tctr = tmp[ctr]; + u128 tctrp1 = tmp[ctr + 1]; + u64 r0 = ((u64)(tctr)) & 0x7ffffffffffffLLU; + u128 c = ((tctr) >> (51)); + tmp[ctr] = ((u128)(r0)); + tmp[ctr + 1] = ((tctrp1) + (c)); + } + { + u32 ctr = 1; + u128 tctr = tmp[ctr]; + u128 tctrp1 = tmp[ctr + 1]; + u64 r0 = ((u64)(tctr)) & 0x7ffffffffffffLLU; + u128 c = ((tctr) >> (51)); + tmp[ctr] = ((u128)(r0)); + tmp[ctr + 1] = ((tctrp1) + (c)); + } + + { + u32 ctr = 2; + u128 tctr = tmp[ctr]; + u128 tctrp1 = tmp[ctr + 1]; + u64 r0 = ((u64)(tctr)) & 0x7ffffffffffffLLU; + u128 c = ((tctr) >> (51)); + tmp[ctr] = ((u128)(r0)); + tmp[ctr + 1] = ((tctrp1) + (c)); + } + { + u32 ctr = 3; + u128 tctr = tmp[ctr]; + u128 tctrp1 = tmp[ctr + 1]; + u64 r0 = ((u64)(tctr)) & 0x7ffffffffffffLLU; + u128 c = ((tctr) >> (51)); + tmp[ctr] = ((u128)(r0)); + tmp[ctr + 1] = ((tctrp1) + (c)); + } +} + +static __always_inline void fmul_shift_reduce(u64 *output) +{ + u64 tmp = output[4]; + u64 b0; + { + u32 ctr = 5 - 0 - 1; + u64 z = output[ctr - 1]; + output[ctr] = z; + } + { + u32 ctr = 5 - 1 - 1; + u64 z = output[ctr - 1]; + output[ctr] = z; + } + { + u32 ctr = 5 - 2 - 1; + u64 z = output[ctr - 1]; + output[ctr] = z; + } + { + u32 ctr = 5 - 3 - 1; + u64 z = output[ctr - 1]; + output[ctr] = z; + } + output[0] = tmp; + b0 = output[0]; + output[0] = 19 * b0; +} + +static __always_inline void fmul_mul_shift_reduce_(u128 *output, u64 *input, + u64 *input21) +{ + u32 i; + u64 input2i; + { + u64 input2i = input21[0]; + fproduct_sum_scalar_multiplication_(output, input, input2i); + fmul_shift_reduce(input); + } + { + u64 input2i = input21[1]; + fproduct_sum_scalar_multiplication_(output, input, input2i); + fmul_shift_reduce(input); + } + { + u64 input2i = input21[2]; + fproduct_sum_scalar_multiplication_(output, input, input2i); + fmul_shift_reduce(input); + } + { + u64 input2i = input21[3]; + fproduct_sum_scalar_multiplication_(output, input, input2i); + fmul_shift_reduce(input); + } + i = 4; + input2i = input21[i]; + fproduct_sum_scalar_multiplication_(output, input, input2i); +} + +static __always_inline void fmul_fmul(u64 *output, u64 *input, u64 *input21) +{ + u64 tmp[5] = { input[0], input[1], input[2], input[3], input[4] }; + { + u128 b4; + u128 b0; + u128 b4_; + u128 b0_; + u64 i0; + u64 i1; + u64 i0_; + u64 i1_; + u128 t[5] = { 0 }; + fmul_mul_shift_reduce_(t, tmp, input21); + fproduct_carry_wide_(t); + b4 = t[4]; + b0 = t[0]; + b4_ = ((b4) & (((u128)(0x7ffffffffffffLLU)))); + b0_ = ((b0) + (((u128)(19) * (((u64)(((b4) >> (51)))))))); + t[4] = b4_; + t[0] = b0_; + fproduct_copy_from_wide_(output, t); + i0 = output[0]; + i1 = output[1]; + i0_ = i0 & 0x7ffffffffffffLLU; + i1_ = i1 + (i0 >> 51); + output[0] = i0_; + output[1] = i1_; + } +} + +static __always_inline void fsquare_fsquare__(u128 *tmp, u64 *output) +{ + u64 r0 = output[0]; + u64 r1 = output[1]; + u64 r2 = output[2]; + u64 r3 = output[3]; + u64 r4 = output[4]; + u64 d0 = r0 * 2; + u64 d1 = r1 * 2; + u64 d2 = r2 * 2 * 19; + u64 d419 = r4 * 19; + u64 d4 = d419 * 2; + u128 s0 = ((((((u128)(r0) * (r0))) + (((u128)(d4) * (r1))))) + + (((u128)(d2) * (r3)))); + u128 s1 = ((((((u128)(d0) * (r1))) + (((u128)(d4) * (r2))))) + + (((u128)(r3 * 19) * (r3)))); + u128 s2 = ((((((u128)(d0) * (r2))) + (((u128)(r1) * (r1))))) + + (((u128)(d4) * (r3)))); + u128 s3 = ((((((u128)(d0) * (r3))) + (((u128)(d1) * (r2))))) + + (((u128)(r4) * (d419)))); + u128 s4 = ((((((u128)(d0) * (r4))) + (((u128)(d1) * (r3))))) + + (((u128)(r2) * (r2)))); + tmp[0] = s0; + tmp[1] = s1; + tmp[2] = s2; + tmp[3] = s3; + tmp[4] = s4; +} + +static __always_inline void fsquare_fsquare_(u128 *tmp, u64 *output) +{ + u128 b4; + u128 b0; + u128 b4_; + u128 b0_; + u64 i0; + u64 i1; + u64 i0_; + u64 i1_; + fsquare_fsquare__(tmp, output); + fproduct_carry_wide_(tmp); + b4 = tmp[4]; + b0 = tmp[0]; + b4_ = ((b4) & (((u128)(0x7ffffffffffffLLU)))); + b0_ = ((b0) + (((u128)(19) * (((u64)(((b4) >> (51)))))))); + tmp[4] = b4_; + tmp[0] = b0_; + fproduct_copy_from_wide_(output, tmp); + i0 = output[0]; + i1 = output[1]; + i0_ = i0 & 0x7ffffffffffffLLU; + i1_ = i1 + (i0 >> 51); + output[0] = i0_; + output[1] = i1_; +} + +static __always_inline void fsquare_fsquare_times_(u64 *output, u128 *tmp, + u32 count1) +{ + u32 i; + fsquare_fsquare_(tmp, output); + for (i = 1; i < count1; ++i) + fsquare_fsquare_(tmp, output); +} + +static __always_inline void fsquare_fsquare_times(u64 *output, u64 *input, + u32 count1) +{ + u128 t[5]; + memcpy(output, input, 5 * sizeof(*input)); + fsquare_fsquare_times_(output, t, count1); +} + +static __always_inline void fsquare_fsquare_times_inplace(u64 *output, + u32 count1) +{ + u128 t[5]; + fsquare_fsquare_times_(output, t, count1); +} + +static __always_inline void crecip_crecip(u64 *out, u64 *z) +{ + u64 buf[20] = { 0 }; + u64 *a0 = buf; + u64 *t00 = buf + 5; + u64 *b0 = buf + 10; + u64 *t01; + u64 *b1; + u64 *c0; + u64 *a; + u64 *t0; + u64 *b; + u64 *c; + fsquare_fsquare_times(a0, z, 1); + fsquare_fsquare_times(t00, a0, 2); + fmul_fmul(b0, t00, z); + fmul_fmul(a0, b0, a0); + fsquare_fsquare_times(t00, a0, 1); + fmul_fmul(b0, t00, b0); + fsquare_fsquare_times(t00, b0, 5); + t01 = buf + 5; + b1 = buf + 10; + c0 = buf + 15; + fmul_fmul(b1, t01, b1); + fsquare_fsquare_times(t01, b1, 10); + fmul_fmul(c0, t01, b1); + fsquare_fsquare_times(t01, c0, 20); + fmul_fmul(t01, t01, c0); + fsquare_fsquare_times_inplace(t01, 10); + fmul_fmul(b1, t01, b1); + fsquare_fsquare_times(t01, b1, 50); + a = buf; + t0 = buf + 5; + b = buf + 10; + c = buf + 15; + fmul_fmul(c, t0, b); + fsquare_fsquare_times(t0, c, 100); + fmul_fmul(t0, t0, c); + fsquare_fsquare_times_inplace(t0, 50); + fmul_fmul(t0, t0, b); + fsquare_fsquare_times_inplace(t0, 5); + fmul_fmul(out, t0, a); +} + +static __always_inline void fsum(u64 *a, u64 *b) +{ + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + a[3] += b[3]; + a[4] += b[4]; +} + +static __always_inline void fdifference(u64 *a, u64 *b) +{ + u64 tmp[5] = { 0 }; + u64 b0; + u64 b1; + u64 b2; + u64 b3; + u64 b4; + memcpy(tmp, b, 5 * sizeof(*b)); + b0 = tmp[0]; + b1 = tmp[1]; + b2 = tmp[2]; + b3 = tmp[3]; + b4 = tmp[4]; + tmp[0] = b0 + 0x3fffffffffff68LLU; + tmp[1] = b1 + 0x3ffffffffffff8LLU; + tmp[2] = b2 + 0x3ffffffffffff8LLU; + tmp[3] = b3 + 0x3ffffffffffff8LLU; + tmp[4] = b4 + 0x3ffffffffffff8LLU; + { + u64 xi = a[0]; + u64 yi = tmp[0]; + a[0] = yi - xi; + } + { + u64 xi = a[1]; + u64 yi = tmp[1]; + a[1] = yi - xi; + } + { + u64 xi = a[2]; + u64 yi = tmp[2]; + a[2] = yi - xi; + } + { + u64 xi = a[3]; + u64 yi = tmp[3]; + a[3] = yi - xi; + } + { + u64 xi = a[4]; + u64 yi = tmp[4]; + a[4] = yi - xi; + } +} + +static __always_inline void fscalar(u64 *output, u64 *b, u64 s) +{ + u128 tmp[5]; + u128 b4; + u128 b0; + u128 b4_; + u128 b0_; + { + u64 xi = b[0]; + tmp[0] = ((u128)(xi) * (s)); + } + { + u64 xi = b[1]; + tmp[1] = ((u128)(xi) * (s)); + } + { + u64 xi = b[2]; + tmp[2] = ((u128)(xi) * (s)); + } + { + u64 xi = b[3]; + tmp[3] = ((u128)(xi) * (s)); + } + { + u64 xi = b[4]; + tmp[4] = ((u128)(xi) * (s)); + } + fproduct_carry_wide_(tmp); + b4 = tmp[4]; + b0 = tmp[0]; + b4_ = ((b4) & (((u128)(0x7ffffffffffffLLU)))); + b0_ = ((b0) + (((u128)(19) * (((u64)(((b4) >> (51)))))))); + tmp[4] = b4_; + tmp[0] = b0_; + fproduct_copy_from_wide_(output, tmp); +} + +static __always_inline void fmul(u64 *output, u64 *a, u64 *b) +{ + fmul_fmul(output, a, b); +} + +static __always_inline void crecip(u64 *output, u64 *input) +{ + crecip_crecip(output, input); +} + +static __always_inline void point_swap_conditional_step(u64 *a, u64 *b, + u64 swap1, u32 ctr) +{ + u32 i = ctr - 1; + u64 ai = a[i]; + u64 bi = b[i]; + u64 x = swap1 & (ai ^ bi); + u64 ai1 = ai ^ x; + u64 bi1 = bi ^ x; + a[i] = ai1; + b[i] = bi1; +} + +static __always_inline void point_swap_conditional5(u64 *a, u64 *b, u64 swap1) +{ + point_swap_conditional_step(a, b, swap1, 5); + point_swap_conditional_step(a, b, swap1, 4); + point_swap_conditional_step(a, b, swap1, 3); + point_swap_conditional_step(a, b, swap1, 2); + point_swap_conditional_step(a, b, swap1, 1); +} + +static __always_inline void point_swap_conditional(u64 *a, u64 *b, u64 iswap) +{ + u64 swap1 = 0 - iswap; + point_swap_conditional5(a, b, swap1); + point_swap_conditional5(a + 5, b + 5, swap1); +} + +static __always_inline void point_copy(u64 *output, u64 *input) +{ + memcpy(output, input, 5 * sizeof(*input)); + memcpy(output + 5, input + 5, 5 * sizeof(*input)); +} + +static __always_inline void addanddouble_fmonty(u64 *pp, u64 *ppq, u64 *p, + u64 *pq, u64 *qmqp) +{ + u64 *qx = qmqp; + u64 *x2 = pp; + u64 *z2 = pp + 5; + u64 *x3 = ppq; + u64 *z3 = ppq + 5; + u64 *x = p; + u64 *z = p + 5; + u64 *xprime = pq; + u64 *zprime = pq + 5; + u64 buf[40] = { 0 }; + u64 *origx = buf; + u64 *origxprime0 = buf + 5; + u64 *xxprime0; + u64 *zzprime0; + u64 *origxprime; + xxprime0 = buf + 25; + zzprime0 = buf + 30; + memcpy(origx, x, 5 * sizeof(*x)); + fsum(x, z); + fdifference(z, origx); + memcpy(origxprime0, xprime, 5 * sizeof(*xprime)); + fsum(xprime, zprime); + fdifference(zprime, origxprime0); + fmul(xxprime0, xprime, z); + fmul(zzprime0, x, zprime); + origxprime = buf + 5; + { + u64 *xx0; + u64 *zz0; + u64 *xxprime; + u64 *zzprime; + u64 *zzzprime; + xx0 = buf + 15; + zz0 = buf + 20; + xxprime = buf + 25; + zzprime = buf + 30; + zzzprime = buf + 35; + memcpy(origxprime, xxprime, 5 * sizeof(*xxprime)); + fsum(xxprime, zzprime); + fdifference(zzprime, origxprime); + fsquare_fsquare_times(x3, xxprime, 1); + fsquare_fsquare_times(zzzprime, zzprime, 1); + fmul(z3, zzzprime, qx); + fsquare_fsquare_times(xx0, x, 1); + fsquare_fsquare_times(zz0, z, 1); + { + u64 *zzz; + u64 *xx; + u64 *zz; + u64 scalar; + zzz = buf + 10; + xx = buf + 15; + zz = buf + 20; + fmul(x2, xx, zz); + fdifference(zz, xx); + scalar = 121665; + fscalar(zzz, zz, scalar); + fsum(zzz, xx); + fmul(z2, zzz, zz); + } + } +} + +static __always_inline void +ladder_smallloop_cmult_small_loop_step(u64 *nq, u64 *nqpq, u64 *nq2, u64 *nqpq2, + u64 *q, u8 byt) +{ + u64 bit0 = (u64)(byt >> 7); + u64 bit; + point_swap_conditional(nq, nqpq, bit0); + addanddouble_fmonty(nq2, nqpq2, nq, nqpq, q); + bit = (u64)(byt >> 7); + point_swap_conditional(nq2, nqpq2, bit); +} + +static __always_inline void +ladder_smallloop_cmult_small_loop_double_step(u64 *nq, u64 *nqpq, u64 *nq2, + u64 *nqpq2, u64 *q, u8 byt) +{ + u8 byt1; + ladder_smallloop_cmult_small_loop_step(nq, nqpq, nq2, nqpq2, q, byt); + byt1 = byt << 1; + ladder_smallloop_cmult_small_loop_step(nq2, nqpq2, nq, nqpq, q, byt1); +} + +static __always_inline void +ladder_smallloop_cmult_small_loop(u64 *nq, u64 *nqpq, u64 *nq2, u64 *nqpq2, + u64 *q, u8 byt, u32 i) +{ + while (i--) { + ladder_smallloop_cmult_small_loop_double_step(nq, nqpq, nq2, + nqpq2, q, byt); + byt <<= 2; + } +} + +static __always_inline void ladder_bigloop_cmult_big_loop(u8 *n1, u64 *nq, + u64 *nqpq, u64 *nq2, + u64 *nqpq2, u64 *q, + u32 i) +{ + while (i--) { + u8 byte = n1[i]; + ladder_smallloop_cmult_small_loop(nq, nqpq, nq2, nqpq2, q, + byte, 4); + } +} + +static void ladder_cmult(u64 *result, u8 *n1, u64 *q) +{ + u64 point_buf[40] = { 0 }; + u64 *nq = point_buf; + u64 *nqpq = point_buf + 10; + u64 *nq2 = point_buf + 20; + u64 *nqpq2 = point_buf + 30; + point_copy(nqpq, q); + nq[0] = 1; + ladder_bigloop_cmult_big_loop(n1, nq, nqpq, nq2, nqpq2, q, 32); + point_copy(result, nq); +} + +static __always_inline void format_fexpand(u64 *output, const u8 *input) +{ + const u8 *x00 = input + 6; + const u8 *x01 = input + 12; + const u8 *x02 = input + 19; + const u8 *x0 = input + 24; + u64 i0, i1, i2, i3, i4, output0, output1, output2, output3, output4; + i0 = get_unaligned_le64(input); + i1 = get_unaligned_le64(x00); + i2 = get_unaligned_le64(x01); + i3 = get_unaligned_le64(x02); + i4 = get_unaligned_le64(x0); + output0 = i0 & 0x7ffffffffffffLLU; + output1 = i1 >> 3 & 0x7ffffffffffffLLU; + output2 = i2 >> 6 & 0x7ffffffffffffLLU; + output3 = i3 >> 1 & 0x7ffffffffffffLLU; + output4 = i4 >> 12 & 0x7ffffffffffffLLU; + output[0] = output0; + output[1] = output1; + output[2] = output2; + output[3] = output3; + output[4] = output4; +} + +static __always_inline void format_fcontract_first_carry_pass(u64 *input) +{ + u64 t0 = input[0]; + u64 t1 = input[1]; + u64 t2 = input[2]; + u64 t3 = input[3]; + u64 t4 = input[4]; + u64 t1_ = t1 + (t0 >> 51); + u64 t0_ = t0 & 0x7ffffffffffffLLU; + u64 t2_ = t2 + (t1_ >> 51); + u64 t1__ = t1_ & 0x7ffffffffffffLLU; + u64 t3_ = t3 + (t2_ >> 51); + u64 t2__ = t2_ & 0x7ffffffffffffLLU; + u64 t4_ = t4 + (t3_ >> 51); + u64 t3__ = t3_ & 0x7ffffffffffffLLU; + input[0] = t0_; + input[1] = t1__; + input[2] = t2__; + input[3] = t3__; + input[4] = t4_; +} + +static __always_inline void format_fcontract_first_carry_full(u64 *input) +{ + format_fcontract_first_carry_pass(input); + modulo_carry_top(input); +} + +static __always_inline void format_fcontract_second_carry_pass(u64 *input) +{ + u64 t0 = input[0]; + u64 t1 = input[1]; + u64 t2 = input[2]; + u64 t3 = input[3]; + u64 t4 = input[4]; + u64 t1_ = t1 + (t0 >> 51); + u64 t0_ = t0 & 0x7ffffffffffffLLU; + u64 t2_ = t2 + (t1_ >> 51); + u64 t1__ = t1_ & 0x7ffffffffffffLLU; + u64 t3_ = t3 + (t2_ >> 51); + u64 t2__ = t2_ & 0x7ffffffffffffLLU; + u64 t4_ = t4 + (t3_ >> 51); + u64 t3__ = t3_ & 0x7ffffffffffffLLU; + input[0] = t0_; + input[1] = t1__; + input[2] = t2__; + input[3] = t3__; + input[4] = t4_; +} + +static __always_inline void format_fcontract_second_carry_full(u64 *input) +{ + u64 i0; + u64 i1; + u64 i0_; + u64 i1_; + format_fcontract_second_carry_pass(input); + modulo_carry_top(input); + i0 = input[0]; + i1 = input[1]; + i0_ = i0 & 0x7ffffffffffffLLU; + i1_ = i1 + (i0 >> 51); + input[0] = i0_; + input[1] = i1_; +} + +static __always_inline void format_fcontract_trim(u64 *input) +{ + u64 a0 = input[0]; + u64 a1 = input[1]; + u64 a2 = input[2]; + u64 a3 = input[3]; + u64 a4 = input[4]; + u64 mask0 = u64_gte_mask(a0, 0x7ffffffffffedLLU); + u64 mask1 = u64_eq_mask(a1, 0x7ffffffffffffLLU); + u64 mask2 = u64_eq_mask(a2, 0x7ffffffffffffLLU); + u64 mask3 = u64_eq_mask(a3, 0x7ffffffffffffLLU); + u64 mask4 = u64_eq_mask(a4, 0x7ffffffffffffLLU); + u64 mask = (((mask0 & mask1) & mask2) & mask3) & mask4; + u64 a0_ = a0 - (0x7ffffffffffedLLU & mask); + u64 a1_ = a1 - (0x7ffffffffffffLLU & mask); + u64 a2_ = a2 - (0x7ffffffffffffLLU & mask); + u64 a3_ = a3 - (0x7ffffffffffffLLU & mask); + u64 a4_ = a4 - (0x7ffffffffffffLLU & mask); + input[0] = a0_; + input[1] = a1_; + input[2] = a2_; + input[3] = a3_; + input[4] = a4_; +} + +static __always_inline void format_fcontract_store(u8 *output, u64 *input) +{ + u64 t0 = input[0]; + u64 t1 = input[1]; + u64 t2 = input[2]; + u64 t3 = input[3]; + u64 t4 = input[4]; + u64 o0 = t1 << 51 | t0; + u64 o1 = t2 << 38 | t1 >> 13; + u64 o2 = t3 << 25 | t2 >> 26; + u64 o3 = t4 << 12 | t3 >> 39; + u8 *b0 = output; + u8 *b1 = output + 8; + u8 *b2 = output + 16; + u8 *b3 = output + 24; + put_unaligned_le64(o0, b0); + put_unaligned_le64(o1, b1); + put_unaligned_le64(o2, b2); + put_unaligned_le64(o3, b3); +} + +static __always_inline void format_fcontract(u8 *output, u64 *input) +{ + format_fcontract_first_carry_full(input); + format_fcontract_second_carry_full(input); + format_fcontract_trim(input); + format_fcontract_store(output, input); +} + +static __always_inline void format_scalar_of_point(u8 *scalar, u64 *point) +{ + u64 *x = point; + u64 *z = point + 5; + u64 buf[10] __aligned(32) = { 0 }; + u64 *zmone = buf; + u64 *sc = buf + 5; + crecip(zmone, z); + fmul(sc, x, zmone); + format_fcontract(scalar, sc); +} + +static void curve25519_generic(u8 mypublic[CURVE25519_KEY_SIZE], + const u8 secret[CURVE25519_KEY_SIZE], + const u8 basepoint[CURVE25519_KEY_SIZE]) +{ + u64 buf0[10] __aligned(32) = { 0 }; + u64 *x0 = buf0; + u64 *z = buf0 + 5; + u64 *q; + format_fexpand(x0, basepoint); + z[0] = 1; + q = buf0; + { + u8 e[32] __aligned(32) = { 0 }; + u8 *scalar; + memcpy(e, secret, 32); + curve25519_clamp_secret(e); + scalar = e; + { + u64 buf[15] = { 0 }; + u64 *nq = buf; + u64 *x = nq; + x[0] = 1; + ladder_cmult(nq, scalar, q); + format_scalar_of_point(mypublic, nq); + memzero_explicit(buf, sizeof(buf)); + } + memzero_explicit(e, sizeof(e)); + } + memzero_explicit(buf0, sizeof(buf0)); +} diff --git a/curve25519.c b/curve25519.c new file mode 100644 index 0000000..1739a9e --- /dev/null +++ b/curve25519.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2020 Jason A. Donenfeld . All Rights Reserved. + */ + +#include "curve25519.h" + +#include +#include + +#ifndef __BYTE_ORDER__ +#include +#if !defined(BYTE_ORDER) || !defined(BIG_ENDIAN) || !defined(LITTLE_ENDIAN) +#error "Unable to determine endianness." +#endif +#define __BYTE_ORDER__ BYTE_ORDER +#define __ORDER_BIG_ENDIAN__ BIG_ENDIAN +#define __ORDER_LITTLE_ENDIAN__ LITTLE_ENDIAN +#endif + +#ifdef __linux__ +#include +typedef __u64 u64; +typedef __u32 u32; +typedef __u8 u8; +typedef __s64 s64; +#else +typedef uint64_t u64, __le64; +typedef uint32_t u32, __le32; +typedef uint8_t u8; +typedef int64_t s64; +#endif +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define le64_to_cpup(a) __builtin_bswap64(*(a)) +#define le32_to_cpup(a) __builtin_bswap32(*(a)) +#define cpu_to_le64(a) __builtin_bswap64(a) +#else +#define le64_to_cpup(a) (*(a)) +#define le32_to_cpup(a) (*(a)) +#define cpu_to_le64(a) (a) +#endif +#ifndef __unused +#define __unused __attribute__((unused)) +#endif +#ifndef __always_inline +#define __always_inline __inline __attribute__((__always_inline__)) +#endif +#ifndef noinline +#define noinline __attribute__((noinline)) +#endif +#ifndef __aligned +#define __aligned(x) __attribute__((aligned(x))) +#endif +#ifndef __force +#define __force +#endif + +static __always_inline __unused __le32 get_unaligned_le32(const u8 *a) +{ + __le32 l; + __builtin_memcpy(&l, a, sizeof(l)); + return le32_to_cpup(&l); +} +static __always_inline __unused __le64 get_unaligned_le64(const u8 *a) +{ + __le64 l; + __builtin_memcpy(&l, a, sizeof(l)); + return le64_to_cpup(&l); +} +static __always_inline __unused void put_unaligned_le64(u64 s, u8 *d) +{ + __le64 l = cpu_to_le64(s); + __builtin_memcpy(d, &l, sizeof(l)); +} + +static noinline void memzero_explicit(void *s, size_t count) +{ + memset(s, 0, count); + asm volatile("": :"r"(s) : "memory"); +} + +#ifdef __SIZEOF_INT128__ +#include "curve25519-hacl64.h" +#else +#include "curve25519-fiat32.h" +#endif + +void curve25519_generate_public(uint8_t pub[static CURVE25519_KEY_SIZE], const uint8_t secret[static CURVE25519_KEY_SIZE]) +{ + static const uint8_t basepoint[CURVE25519_KEY_SIZE] __aligned(sizeof(uintptr_t)) = { 9 }; + + curve25519(pub, secret, basepoint); +} + +void curve25519(uint8_t mypublic[static CURVE25519_KEY_SIZE], const uint8_t secret[static CURVE25519_KEY_SIZE], const uint8_t basepoint[static CURVE25519_KEY_SIZE]) +{ + curve25519_generic(mypublic, secret, basepoint); +} diff --git a/curve25519.h b/curve25519.h new file mode 100644 index 0000000..1569824 --- /dev/null +++ b/curve25519.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. + */ + +#ifndef CURVE25519_H +#define CURVE25519_H + +#include +#include + +enum curve25519_lengths { + CURVE25519_KEY_SIZE = 32 +}; + +void curve25519(uint8_t mypublic[static CURVE25519_KEY_SIZE], const uint8_t secret[static CURVE25519_KEY_SIZE], const uint8_t basepoint[static CURVE25519_KEY_SIZE]); +void curve25519_generate_public(uint8_t pub[static CURVE25519_KEY_SIZE], const uint8_t secret[static CURVE25519_KEY_SIZE]); +static inline void curve25519_clamp_secret(uint8_t secret[static CURVE25519_KEY_SIZE]) +{ + secret[0] &= 248; + secret[31] = (secret[31] & 127) | 64; +} + +#endif diff --git a/examples/net0-ap1.key b/examples/net0-ap1.key new file mode 100644 index 0000000..9896b9f --- /dev/null +++ b/examples/net0-ap1.key @@ -0,0 +1 @@ +0GFjANSeGOgsKPQrXm64K8qcf8B5dZGa830SOMGZq0A= diff --git a/examples/net0-ap2.key b/examples/net0-ap2.key new file mode 100644 index 0000000..41b8d29 --- /dev/null +++ b/examples/net0-ap2.key @@ -0,0 +1 @@ ++E8d8Yjqd9KFRE4yWccEUNIdVABD/2FwNB8lC43K3kg= diff --git a/examples/net0-master.key b/examples/net0-master.key new file mode 100644 index 0000000..2b55d66 --- /dev/null +++ b/examples/net0-master.key @@ -0,0 +1 @@ +wC0/vC8jGcYZnu0ncTYQa/XM2NdZWmP06Gb+/eHOOnc= diff --git a/examples/net0.json b/examples/net0.json new file mode 100644 index 0000000..567d508 --- /dev/null +++ b/examples/net0.json @@ -0,0 +1,40 @@ +{ + "config": { + "port": 3456, + "peer-exchange-port": 3458, + "keepalive": 10 + }, + + "hosts": { + "master": { + "key": "25sPrbtEtIiANFr00tC5MS2UMfXmHFj/AJyDi4wR8n4=", + "endpoint": "192.168.1.3", + "subnet": [ "192.168.3.0/24" ], + "ipaddr": [ "192.168.3.1" ] + }, + "ap1": { + "key": "mxQQxpwinlDxy0bp564b25il1oDiaf/a8jkaKQBcjw4=", + "groups": [ "ap" ], + "subnet": [ "192.168.4.0/24" ], + "ipaddr": [ "192.168.4.1" ], + "port": 3457 + }, + "ap2": { + "key": "+hiP+1FZci9Hp44gWEPigbsMHMe6De7nnMbVDJFhDjU=", + "groups": [ "ap" ], + "subnet": [ "192.168.5.0/24" ], + "ipaddr": [ "192.168.5.1" ], + "port": 3457 + } + }, + + "services": { + "l2-tunnel": { + "type": "vxlan", + "members": [ "master", "@ap" ] + }, + "usteer": { + "members": [ "@ap" ] + } + } +} diff --git a/examples/test-net0.sh b/examples/test-net0.sh new file mode 100755 index 0000000..5089ee1 --- /dev/null +++ b/examples/test-net0.sh @@ -0,0 +1,11 @@ +#!/bin/sh +ifname="${1:-wg0}" +host="${2:-ap1}" + +../unetd -d -h $PWD/hosts -N '{ + "name": "'"$ifname"'", + "type": "file", + "key": "'"$(cat ./net0-${host}.key)"'", + "file": "'"$PWD/net0.json"'", + "update-cmd": "'"$PWD/../scripts/update-cmd.pl"'" +}' diff --git a/host.c b/host.c new file mode 100644 index 0000000..cd952f5 --- /dev/null +++ b/host.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include "unetd.h" + +static LIST_HEAD(old_hosts); + +static int avl_key_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, CURVE25519_KEY_SIZE); +} + +static bool +network_peer_equal(struct network_peer *p1, struct network_peer *p2) +{ + return !memcmp(&p1->local_addr, &p2->local_addr, sizeof(p1->local_addr)) && + blob_attr_equal(p1->ipaddr, p2->ipaddr) && + blob_attr_equal(p1->subnet, p2->subnet) && + p1->port == p2->port; +} + +static void +network_peer_update(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct network *net = container_of(tree, struct network, peers); + struct network_peer *h_new = container_of_safe(node_new, struct network_peer, node); + struct network_peer *h_old = container_of_safe(node_old, struct network_peer, node); + int ret; + + if (h_new && h_old) { + memcpy(&h_new->state, &h_old->state, sizeof(h_new->state)); + + if (network_peer_equal(h_new, h_old)) + return; + } + + if (h_new) + ret = wg_peer_update(net, h_new, h_old ? WG_PEER_UPDATE : WG_PEER_CREATE); + else + ret = wg_peer_update(net, h_old, WG_PEER_DELETE); + + if (ret) + fprintf(stderr, "Failed to %s peer on network %s: %s\n", + h_new ? "update" : "delete", network_name(net), + strerror(-ret)); +} + +static struct network_group * +network_group_get(struct network *net, const char *name) +{ + struct network_group *group; + char *name_buf; + + group = avl_find_element(&net->groups, name, group, node); + if (group) + return group; + + group = calloc_a(sizeof(*group), &name_buf, strlen(name) + 1); + group->node.key = strcpy(name_buf, name); + avl_insert(&net->groups, &group->node); + + return group; +} + +static void +network_host_add_group(struct network *net, struct network_host *host, + const char *name) +{ + struct network_group *group; + int i; + + group = network_group_get(net, name); + for (i = 0; i < group->n_members; i++) + if (group->members[i] == host) + return; + + group->n_members++; + group->members = realloc(group->members, group->n_members * sizeof(*group->members)); + group->members[group->n_members - 1] = host; +} + +static void +network_host_create(struct network *net, struct blob_attr *attr) +{ + enum { + NETWORK_HOST_KEY, + NETWORK_HOST_GROUPS, + NETWORK_HOST_IPADDR, + NETWORK_HOST_SUBNET, + NETWORK_HOST_PORT, + NETWORK_HOST_ENDPOINT, + __NETWORK_HOST_MAX + }; + static const struct blobmsg_policy policy[__NETWORK_HOST_MAX] = { + [NETWORK_HOST_KEY] = { "key", BLOBMSG_TYPE_STRING }, + [NETWORK_HOST_GROUPS] = { "groups", BLOBMSG_TYPE_ARRAY }, + [NETWORK_HOST_IPADDR] = { "ipaddr", BLOBMSG_TYPE_ARRAY }, + [NETWORK_HOST_SUBNET] = { "subnet", BLOBMSG_TYPE_ARRAY }, + [NETWORK_HOST_PORT] = { "port", BLOBMSG_TYPE_INT32 }, + [NETWORK_HOST_ENDPOINT] = { "endpoint", BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[__NETWORK_HOST_MAX]; + struct blob_attr *cur, *ipaddr, *subnet; + uint8_t key[CURVE25519_KEY_SIZE]; + struct network_host *host; + struct network_peer *peer; + int ipaddr_len, subnet_len; + const char *name, *endpoint; + char *name_buf, *endpoint_buf; + int rem; + + blobmsg_parse(policy, __NETWORK_HOST_MAX, tb, blobmsg_data(attr), blobmsg_len(attr)); + + if (!tb[NETWORK_HOST_KEY]) + return; + + ipaddr_len = tb[NETWORK_HOST_IPADDR] ? blob_pad_len(tb[NETWORK_HOST_IPADDR]) : 0; + if (ipaddr_len && + blobmsg_check_array(tb[NETWORK_HOST_IPADDR], BLOBMSG_TYPE_STRING) < 0) + ipaddr_len = 0; + + subnet_len = tb[NETWORK_HOST_SUBNET] ? blob_pad_len(tb[NETWORK_HOST_SUBNET]) : 0; + if (subnet_len && + blobmsg_check_array(tb[NETWORK_HOST_SUBNET], BLOBMSG_TYPE_STRING) < 0) + subnet_len = 0; + + if ((cur = tb[NETWORK_HOST_ENDPOINT]) != NULL) + endpoint = blobmsg_get_string(cur); + else + endpoint = NULL; + + if (b64_decode(blobmsg_get_string(tb[NETWORK_HOST_KEY]), key, + sizeof(key)) != sizeof(key)) + return; + + name = blobmsg_name(attr); + host = avl_find_element(&net->hosts, name, host, node); + if (host) + return; + + host = calloc_a(sizeof(*host), + &name_buf, strlen(name) + 1, + &ipaddr, ipaddr_len, + &subnet, subnet_len, + &endpoint_buf, endpoint ? strlen(endpoint) + 1 : 0); + peer = &host->peer; + if ((cur = tb[NETWORK_HOST_IPADDR]) != NULL && ipaddr_len) + peer->ipaddr = memcpy(ipaddr, cur, ipaddr_len); + if ((cur = tb[NETWORK_HOST_SUBNET]) != NULL && subnet_len) + peer->subnet = memcpy(subnet, cur, subnet_len); + if ((cur = tb[NETWORK_HOST_PORT]) != NULL) + peer->port = blobmsg_get_u32(cur); + else + peer->port = net->net_config.port; + if (endpoint) + peer->endpoint = strcpy(endpoint_buf, endpoint); + memcpy(peer->key, key, sizeof(key)); + host->node.key = strcpy(name_buf, name); + + memcpy(&peer->local_addr.network_id, + &net->net_config.addr.network_id, + sizeof(peer->local_addr.network_id)); + network_fill_host_addr(&peer->local_addr, peer->key); + + blobmsg_for_each_attr(cur, tb[NETWORK_HOST_GROUPS], rem) { + if (!blobmsg_check_attr(cur, false) || + blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + network_host_add_group(net, host, blobmsg_get_string(cur)); + } + + avl_insert(&net->hosts, &host->node); + if (!memcmp(peer->key, net->config.pubkey, sizeof(key))) { + if (!net->prev_local_host || + !network_peer_equal(&net->prev_local_host->peer, &host->peer)) + net->net_config.local_host_changed = true; + + net->net_config.local_host = host; + } +} + +void network_hosts_update_start(struct network *net) +{ + struct network_host *host, *htmp; + struct network_group *group, *gtmp; + + avl_remove_all_elements(&net->hosts, host, node, htmp) + list_add_tail(&host->node.list, &old_hosts); + + avl_remove_all_elements(&net->groups, group, node, gtmp) { + free(group->members); + free(group); + } + + vlist_update(&net->peers); +} + +void network_hosts_update_done(struct network *net) +{ + struct network_host *host, *tmp; + + if (!net->net_config.local_host) + goto out; + + if (net->net_config.local_host_changed) + wg_init_local(net, &net->net_config.local_host->peer); + + avl_for_each_element(&net->hosts, host, node) + if (host != net->net_config.local_host) + vlist_add(&net->peers, &host->peer.node, host->peer.key); + +out: + vlist_flush(&net->peers); + + list_for_each_entry_safe(host, tmp, &old_hosts, node.list) { + list_del(&host->node.list); + free(host); + } +} + +static void +network_hosts_connect_cb(struct uloop_timeout *t) +{ + struct network *net = container_of(t, struct network, connect_timer); + struct network_host *host; + union network_endpoint *ep; + + if (!net->net_config.keepalive) + return; + + wg_peer_refresh(net); + + avl_for_each_element(&net->hosts, host, node) { + struct network_peer *peer = &host->peer; + + if (host == net->net_config.local_host) + continue; + + if (peer->state.connected) + continue; + + ep = &peer->state.next_endpoint; + if (peer->endpoint && + network_get_endpoint(ep, peer->endpoint, peer->port, + peer->state.connect_attempt++)) + continue; + + if (!ep->sa.sa_family) + continue; + + if (memcmp(ep, &peer->state.endpoint, sizeof(*ep)) != 0) + unetd_ubus_netifd_add_route(net, ep); + + wg_peer_connect(net, peer, ep); + } + + network_pex_event(net, NULL, PEX_EV_QUERY); + + uloop_timeout_set(t, 1000); +} + +void network_hosts_add(struct network *net, struct blob_attr *hosts) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, hosts, rem) + network_host_create(net, cur); +} + +void network_hosts_init(struct network *net) +{ + avl_init(&net->hosts, avl_strcmp, false, NULL); + vlist_init(&net->peers, avl_key_cmp, network_peer_update); + avl_init(&net->groups, avl_strcmp, false, NULL); + net->connect_timer.cb = network_hosts_connect_cb; +} + +void network_hosts_free(struct network *net) +{ + uloop_timeout_cancel(&net->connect_timer); + network_hosts_update_start(net); + network_hosts_update_done(net); +} diff --git a/host.h b/host.h new file mode 100644 index 0000000..ea8e866 --- /dev/null +++ b/host.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_HOST_H +#define __UNETD_HOST_H + +struct network_peer { + struct vlist_node node; + uint8_t key[CURVE25519_KEY_SIZE]; + union network_addr local_addr; + const char *endpoint; + struct blob_attr *ipaddr; + struct blob_attr *subnet; + int port; + + struct { + int connect_attempt; + bool connected; + bool handshake; + bool has_local_ep_addr; + union network_addr local_ep_addr; + union network_endpoint endpoint; + + union network_endpoint next_endpoint; + uint64_t last_ep_update; + + uint64_t rx_bytes; + uint64_t last_handshake; + uint64_t last_request; + int idle; + } state; +}; + +struct network_host { + struct avl_node node; + + struct network_peer peer; +}; + +struct network_group { + struct avl_node node; + const char *name; + + int n_members; + struct network_host **members; +}; + +static inline const char *network_host_name(struct network_host *host) +{ + return host->node.key; +} + +static inline const char *network_peer_name(struct network_peer *peer) +{ + struct network_host *host = container_of(peer, struct network_host, peer); + + return network_host_name(host); +} + +void network_hosts_update_start(struct network *net); +void network_hosts_update_done(struct network *net); +void network_hosts_add(struct network *net, struct blob_attr *hosts); + +void network_hosts_init(struct network *net); +void network_hosts_free(struct network *net); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..74fe964 --- /dev/null +++ b/main.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include "unetd.h" + +struct cmdline_network { + struct cmdline_network *next; + char *data; +}; + +static struct cmdline_network *cmd_nets; +static const char *hosts_file; +bool dummy_mode; +bool debug; + +static void +network_write_hosts(struct network *net, FILE *f) +{ + struct network_host *host; + char ip[INET6_ADDRSTRLEN]; + + if (!net->net_config.local_host) + return; + + avl_for_each_element(&net->hosts, host, node) { + inet_ntop(AF_INET6, &host->peer.local_addr, ip, sizeof(ip)); + fprintf(f, "%s\t%s\n", ip, network_host_name(host)); + } +} + +void unetd_write_hosts(void) +{ + struct network *net; + char *tmpfile = NULL; + FILE *f; + int fd; + + if (!hosts_file) + return; + + asprintf(&tmpfile, "%s.XXXXXXXX", hosts_file); + fd = mkstemp(tmpfile); + if (fd < 0) { + perror("mkstemp"); + goto out; + } + + chmod(tmpfile, 0644); + f = fdopen(fd, "w"); + if (!f) { + close(fd); + goto out; + } + + avl_for_each_element(&networks, net, node) + network_write_hosts(net, f); + + fclose(f); + + if (rename(tmpfile, hosts_file)) + unlink(tmpfile); + +out: + free(tmpfile); +} + +static void add_networks(void) +{ + struct cmdline_network *net; + static struct blob_buf b; + struct blob_attr *name; + + for (net = cmd_nets; net; net = net->next) { + blob_buf_init(&b, 0); + if (!blobmsg_add_json_from_string(&b, net->data)) + continue; + + blobmsg_parse(&network_policy[NETWORK_ATTR_NAME], 1, &name, + blobmsg_data(b.head), blobmsg_len(b.head)); + if (!name) + continue; + + unetd_network_add(blobmsg_get_string(name), b.head); + } + + blob_buf_free(&b); +} + +int main(int argc, char **argv) +{ + struct cmdline_network *net; + int ch; + + while ((ch = getopt(argc, argv, "Ddh:N:")) != -1) { + switch (ch) { + case 'd': + debug = true; + break; + case 'D': + dummy_mode = true; + break; + case 'h': + hosts_file = optarg; + break; + case 'N': + net = calloc(1, sizeof(*net)); + net->next = cmd_nets; + net->data = optarg; + cmd_nets = net; + break; + } + } + + uloop_init(); + unetd_ubus_init(); + unetd_write_hosts(); + add_networks(); + uloop_run(); + network_free_all(); + uloop_done(); + + return 0; +} diff --git a/network.c b/network.c new file mode 100644 index 0000000..a6259ed --- /dev/null +++ b/network.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include +#include "unetd.h" + +enum { + NETDATA_ATTR_CONFIG, + NETDATA_ATTR_HOSTS, + NETDATA_ATTR_GROUPS, + NETDATA_ATTR_SERVICES, + __NETDATA_ATTR_MAX, +}; + +static const struct blobmsg_policy netdata_policy[__NETDATA_ATTR_MAX] = { + [NETDATA_ATTR_CONFIG] = { "config", BLOBMSG_TYPE_TABLE }, + [NETDATA_ATTR_HOSTS] = { "hosts", BLOBMSG_TYPE_TABLE }, + [NETDATA_ATTR_SERVICES] = { "services", BLOBMSG_TYPE_TABLE }, +}; + +enum { + NETCONF_ATTR_ID, + NETCONF_ATTR_PORT, + NETCONF_ATTR_PEX_PORT, + NETCONF_ATTR_KEEPALIVE, + __NETCONF_ATTR_MAX +}; + +static const struct blobmsg_policy netconf_policy[__NETCONF_ATTR_MAX] = { + [NETCONF_ATTR_ID] = { "id", BLOBMSG_TYPE_STRING }, + [NETCONF_ATTR_PORT] = { "port", BLOBMSG_TYPE_INT32 }, + [NETCONF_ATTR_PEX_PORT] = { "peer-exchange-port", BLOBMSG_TYPE_INT32 }, + [NETCONF_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 }, +}; + +const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = { + [NETWORK_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_KEY] = { "key", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_FILE] = { "file", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_DATA] = { "data", BLOBMSG_TYPE_TABLE }, + [NETWORK_ATTR_INTERFACE] = { "interface", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 }, + [NETWORK_ATTR_DOMAIN] = { "domain", BLOBMSG_TYPE_STRING }, + [NETWORK_ATTR_UPDATE_CMD] = { "update-cmd", BLOBMSG_TYPE_STRING }, +}; + +AVL_TREE(networks, avl_strcmp, false, NULL); +static struct blob_buf b; + +static void network_load_config_data(struct network *net, struct blob_attr *data) +{ + struct blob_attr *tb[__NETCONF_ATTR_MAX]; + struct blob_attr *cur; + siphash_key_t key = {}; + + blobmsg_parse(netconf_policy, __NETCONF_ATTR_MAX, tb, + blobmsg_data(data), blobmsg_len(data)); + + if ((cur = tb[NETCONF_ATTR_PORT]) != NULL) + net->net_config.port = blobmsg_get_u32(cur); + else + net->net_config.port = 51820; + + if ((cur = tb[NETCONF_ATTR_PEX_PORT]) != NULL) + net->net_config.pex_port = blobmsg_get_u32(cur); + + if ((cur = tb[NETCONF_ATTR_ID]) != NULL) { + const char *id = blobmsg_get_string(cur); + siphash_to_le64(&net->net_config.addr.network_id, id, strlen(id), &key); + } else { + siphash_to_le64(&net->net_config.addr.network_id, &net->net_config.port, + sizeof(net->net_config.port), &key); + } + + net->net_config.addr.network_id[0] = 0xfd; + network_fill_host_addr(&net->net_config.addr, net->config.pubkey); + + if (net->config.keepalive >= 0) + net->net_config.keepalive = net->config.keepalive; + else if ((cur = tb[NETCONF_ATTR_KEEPALIVE]) != NULL) + net->net_config.keepalive = blobmsg_get_u32(cur); + else + net->net_config.keepalive = 0; +} + +static int network_load_data(struct network *net, struct blob_attr *data) +{ + struct blob_attr *tb[__NETDATA_ATTR_MAX]; + + blobmsg_parse(netdata_policy, __NETDATA_ATTR_MAX, tb, + blobmsg_data(data), blobmsg_len(data)); + + network_load_config_data(net, tb[NETDATA_ATTR_CONFIG]); + network_hosts_add(net, tb[NETDATA_ATTR_HOSTS]); + network_services_add(net, tb[NETDATA_ATTR_SERVICES]); + + return 0; +} + +static int network_load_file(struct network *net) +{ + blob_buf_init(&b, 0); + + if (!blobmsg_add_json_from_file(&b, net->config.file)) + return -1; + + return network_load_data(net, b.head); +} + +static void +network_fill_ip(struct blob_buf *buf, int af, union network_addr *addr, int mask) +{ + char *str; + void *c; + + c = blobmsg_open_table(buf, NULL); + + blobmsg_printf(buf, "mask", "%d", mask); + + str = blobmsg_alloc_string_buffer(buf, "ipaddr", INET6_ADDRSTRLEN); + inet_ntop(af, addr, str, INET6_ADDRSTRLEN); + blobmsg_add_string_buffer(buf); + + blobmsg_close_table(buf, c); +} + +static void +network_fill_ipaddr_list(struct network_host *host, struct blob_buf *b, bool ipv6) +{ + union network_addr addr = {}; + struct blob_attr *cur; + void *c; + int rem; + int af; + + af = ipv6 ? AF_INET6 : AF_INET; + blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) { + const char *str = blobmsg_get_string(cur); + + if (!!strchr(str, ':') != ipv6) + continue; + + if (inet_pton(af, str, &addr) != 1) + continue; + + c = blobmsg_open_table(b, NULL); + blobmsg_add_string(b, "ipaddr", str); + blobmsg_add_string(b, "mask", ipv6 ? "128" : "32"); + blobmsg_close_table(b, c); + } +} + +static void +network_fill_ip_settings(struct network *net, struct blob_buf *buf) +{ + struct network_host *host = net->net_config.local_host; + void *c; + + c = blobmsg_open_array(buf, "ipaddr"); + network_fill_ipaddr_list(host, buf, false); + blobmsg_close_array(buf, c); + + c = blobmsg_open_array(buf, "ip6addr"); + network_fill_ip(buf, AF_INET6, &host->peer.local_addr, 64); + network_fill_ipaddr_list(host, buf, true); + blobmsg_close_array(buf, c); +} + +static void +__network_fill_host_subnets(struct network_host *host, struct blob_buf *b, bool ipv6) +{ + union network_addr addr = {}; + struct blob_attr *cur; + void *c; + int af; + int mask; + int rem; + + af = ipv6 ? AF_INET6 : AF_INET; + blobmsg_for_each_attr(cur, host->peer.subnet, rem) { + const char *str = blobmsg_get_string(cur); + char *buf; + + if (!!strchr(str, ':') != ipv6) + continue; + + if (network_get_subnet(af, &addr, &mask, str)) + continue; + + c = blobmsg_open_table(b, NULL); + + buf = blobmsg_alloc_string_buffer(b, "target", INET6_ADDRSTRLEN); + inet_ntop(af, &addr, buf, INET6_ADDRSTRLEN); + blobmsg_add_string_buffer(b); + + blobmsg_printf(b, "netmask", "%d", mask); + + blobmsg_close_table(b, c); + } + + blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) { + const char *str = blobmsg_get_string(cur); + + if (!!strchr(str, ':') != ipv6) + continue; + + if (inet_pton(af, str, &addr) != 1) + continue; + + c = blobmsg_open_table(b, NULL); + blobmsg_add_string(b, "target", str); + blobmsg_add_string(b, "netmask", ipv6 ? "128" : "32"); + blobmsg_close_table(b, c); + } +} + +static void +__network_fill_subnets(struct network *net, struct blob_buf *buf, bool ipv6) +{ + struct network_host *host; + void *c; + + c = blobmsg_open_array(buf, ipv6 ? "routes6": "routes"); + avl_for_each_element(&net->hosts, host, node) { + if (host == net->net_config.local_host) + continue; + __network_fill_host_subnets(host, buf, ipv6); + } + blobmsg_close_array(buf, c); +} + + +static void +network_fill_subnets(struct network *net, struct blob_buf *buf) +{ + __network_fill_subnets(net, buf, false); + __network_fill_subnets(net, buf, true); +} + +static void +network_do_update(struct network *net, bool up) +{ + if (!net->net_config.local_host) + up = false; + + blob_buf_init(&b, 0); + blobmsg_add_u32(&b, "action", 0); + blobmsg_add_string(&b, "ifname", network_name(net)); + blobmsg_add_u8(&b, "link-up", up); + + if (up) { + network_fill_ip_settings(net, &b); + network_fill_subnets(net, &b); + } + + if (debug) { + char *s = blobmsg_format_json(b.head, true); + D_NET(net, "update: %s", s); + free(s); + } + + if (net->config.update_cmd) { + const char *argv[] = { net->config.update_cmd, NULL, NULL }; + int pid, stat; + + pid = fork(); + if (pid == 0) { + argv[1] = blobmsg_format_json(b.head, true); + execvp(argv[0], (char **)argv); + exit(1); + } + waitpid(pid, &stat, 0); + } + + if (!net->config.interface) + return; + + blobmsg_add_string(&b, "interface", net->config.interface); + unetd_ubus_netifd_update(b.head); +} + +static int network_reload(struct network *net) +{ + int ret; + + net->prev_local_host = net->net_config.local_host; + + memset(&net->net_config, 0, sizeof(net->net_config)); + + network_pex_close(net); + network_services_free(net); + network_hosts_update_start(net); + + switch (net->config.type) { + case NETWORK_TYPE_FILE: + ret = network_load_file(net); + break; + case NETWORK_TYPE_INLINE: + ret = network_load_data(net, net->config.net_data); + break; + } + + network_hosts_update_done(net); + uloop_timeout_set(&net->connect_timer, 10); + + net->prev_local_host = NULL; + + unetd_write_hosts(); + network_do_update(net, true); + network_pex_open(net); + + return ret; +} + +static int network_setup(struct network *net) +{ + if (wg_init_network(net)) { + fprintf(stderr, "Setup failed for network %s\n", network_name(net)); + return -1; + } + + return 0; +} + +static void network_teardown(struct network *net) +{ + network_do_update(net, false); + network_pex_close(net); + network_hosts_free(net); + network_services_free(net); + wg_cleanup_network(net); +} + +static void +network_destroy(struct network *net) +{ + network_teardown(net); + avl_delete(&networks, &net->node); + free(net->config.data); + free(net); +} + +static int +network_set_config(struct network *net, struct blob_attr *config) +{ + struct blob_attr *tb[__NETWORK_ATTR_MAX]; + struct blob_attr *cur; + + if (net->config.data && blob_attr_equal(net->config.data, config)) + goto reload; + + network_teardown(net); + + free(net->config.data); + memset(&net->config, 0, sizeof(net->config)); + + net->config.data = blob_memdup(config); + blobmsg_parse(network_policy, __NETWORK_ATTR_MAX, tb, + blobmsg_data(net->config.data), + blobmsg_len(net->config.data)); + + if ((cur = tb[NETWORK_ATTR_TYPE]) == NULL) + goto invalid; + + if (!strcmp(blobmsg_get_string(cur), "file")) + net->config.type = NETWORK_TYPE_FILE; + else if (!strcmp(blobmsg_get_string(cur), "inline")) + net->config.type = NETWORK_TYPE_INLINE; + else + goto invalid; + + if ((cur = tb[NETWORK_ATTR_KEEPALIVE]) != NULL) + net->config.keepalive = blobmsg_get_u32(cur); + else + net->config.keepalive = -1; + + switch (net->config.type) { + case NETWORK_TYPE_FILE: + if ((cur = tb[NETWORK_ATTR_FILE]) != NULL) + net->config.file = blobmsg_get_string(cur); + else + goto invalid; + break; + case NETWORK_TYPE_INLINE: + net->config.net_data = tb[NETWORK_ATTR_DATA]; + if (!net->config.net_data) + goto invalid; + break; + } + + if ((cur = tb[NETWORK_ATTR_INTERFACE]) != NULL) + net->config.interface = blobmsg_get_string(cur); + + if ((cur = tb[NETWORK_ATTR_UPDATE_CMD]) != NULL) + net->config.update_cmd = blobmsg_get_string(cur); + + if ((cur = tb[NETWORK_ATTR_DOMAIN]) != NULL) + net->config.domain = blobmsg_get_string(cur); + + if ((cur = tb[NETWORK_ATTR_KEY]) == NULL) + goto invalid; + + if (b64_decode(blobmsg_get_string(cur), net->config.key, sizeof(net->config.key)) != + sizeof(net->config.key)) + goto invalid; + + curve25519_generate_public(net->config.pubkey, net->config.key); + + if (network_setup(net)) + goto invalid; + +reload: + network_reload(net); + + return 0; + +invalid: + network_destroy(net); + return -1; +} + +static struct network * +network_alloc(const char *name) +{ + struct network *net; + char *name_buf; + + net = calloc_a(sizeof(*net), &name_buf, strlen(name) + 1); + net->node.key = strcpy(name_buf, name); + avl_insert(&networks, &net->node); + + network_pex_init(net); + network_hosts_init(net); + network_services_init(net); + + return net; +} + +void network_fill_host_addr(union network_addr *addr, uint8_t *pubkey) +{ + siphash_key_t key = { + .key = { + get_unaligned_le64(addr->network_id), + get_unaligned_le64(addr->network_id) + } + }; + + siphash_to_le64(&addr->host_addr, pubkey, CURVE25519_KEY_SIZE, &key); +} + +int unetd_network_add(const char *name, struct blob_attr *config) +{ + struct network *net; + + if (strchr(name, '/')) + return -1; + + net = avl_find_element(&networks, name, net, node); + if (!net) + net = network_alloc(name); + + return network_set_config(net, config); +} + +int unetd_network_remove(const char *name) +{ + struct network *net; + + net = avl_find_element(&networks, name, net, node); + if (!net) + return -1; + + network_destroy(net); + + return 0; +} + +void network_free_all(void) +{ + struct network *net, *tmp; + + avl_for_each_element_safe(&networks, net, node, tmp) + network_destroy(net); +} diff --git a/network.h b/network.h new file mode 100644 index 0000000..7e023fb --- /dev/null +++ b/network.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_NETWORK_H +#define __UNETD_NETWORK_H + +#include +#include +#include "curve25519.h" + +enum network_type { + NETWORK_TYPE_FILE, + NETWORK_TYPE_INLINE, +}; + +struct wg_ops; +struct network_group; +struct network_host; + +struct network { + struct avl_node node; + + struct wg wg; + + struct { + struct blob_attr *data; + enum network_type type; + int keepalive; + uint8_t key[CURVE25519_KEY_SIZE]; + uint8_t pubkey[CURVE25519_KEY_SIZE]; + const char *file; + const char *interface; + const char *update_cmd; + const char *domain; + struct blob_attr *net_data; + } config; + + struct { + union network_addr addr; + struct network_host *local_host; + unsigned int keepalive; + int port; + int pex_port; + bool local_host_changed; + } net_config; + + struct network_host *prev_local_host; + struct avl_tree hosts; + struct vlist_tree peers; + + struct avl_tree groups; + struct avl_tree services; + + struct uloop_timeout connect_timer; + + struct network_pex pex; +}; + +enum { + NETWORK_ATTR_NAME, + NETWORK_ATTR_TYPE, + NETWORK_ATTR_KEY, + NETWORK_ATTR_FILE, + NETWORK_ATTR_DATA, + NETWORK_ATTR_INTERFACE, + NETWORK_ATTR_UPDATE_CMD, + NETWORK_ATTR_KEEPALIVE, + NETWORK_ATTR_DOMAIN, + __NETWORK_ATTR_MAX, +}; + +extern struct avl_tree networks; +extern const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX]; + +static inline const char *network_name(struct network *net) +{ + return net->node.key; +} + +void network_fill_host_addr(union network_addr *addr, uint8_t *key); +void network_free_all(void); + +int unetd_network_add(const char *name, struct blob_attr *config); +int unetd_network_remove(const char *name); + +#endif diff --git a/pex.c b/pex.c new file mode 100644 index 0000000..381f6b6 --- /dev/null +++ b/pex.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include +#include +#include "unetd.h" + +#define PEX_BUF_SIZE 1024 + +enum pex_opcode { + PEX_MSG_HELLO, + PEX_MSG_NOTIFY_PEERS, + PEX_MSG_QUERY, + PEX_MSG_PING, + PEX_MSG_PONG, +}; + +#define PEX_ID_LEN 8 + +struct pex_hdr { + uint8_t version; + uint8_t opcode; + uint16_t len; + uint8_t id[PEX_ID_LEN]; +}; + +#define PEER_EP_F_IPV6 (1 << 0) +#define PEER_EP_F_LOCAL (1 << 1) + +struct pex_peer_endpoint { + uint16_t flags; + uint16_t port; + uint8_t peer_id[PEX_ID_LEN]; + uint8_t addr[16]; +}; + +struct pex_hello { + uint16_t flags; + uint8_t local_addr[16]; +}; + +static char tx_buf[PEX_BUF_SIZE]; + +static const char *pex_peer_id_str(const uint8_t *key) +{ + static char str[20]; + int i; + + for (i = 0; i < 8; i++) + sprintf(str + i * 2, "%02x", key[i]); + + return str; +} + + +static struct network_peer * +pex_msg_peer(struct network *net, const uint8_t *id) +{ + struct network_peer *peer; + uint8_t key[WG_KEY_LEN] = {}; + + memcpy(key, id, PEX_ID_LEN); + peer = avl_find_ge_element(&net->peers.avl, key, peer, node.avl); + if (!peer || memcmp(peer->key, key, PEX_ID_LEN) != 0) { + D_NET(net, "can't find peer %s", pex_peer_id_str(id)); + return NULL; + } + + return peer; +} + +static struct pex_hdr *pex_msg_init(struct network *net, uint8_t opcode) +{ + struct network_peer *local = &net->net_config.local_host->peer; + struct pex_hdr *hdr = (struct pex_hdr *)tx_buf; + + hdr->version = 0; + hdr->opcode = opcode; + hdr->len = 0; + memcpy(hdr->id, local->key, sizeof(hdr->id)); + + return hdr; +} + +static void *pex_msg_append(size_t len) +{ + struct pex_hdr *hdr = (struct pex_hdr *)tx_buf; + int ofs = hdr->len + sizeof(struct pex_hdr); + void *buf = &tx_buf[ofs]; + + if (sizeof(tx_buf) - ofs < len) + return NULL; + + hdr->len += len; + memset(buf, 0, len); + + return buf; +} + +static void pex_msg_send(struct network *net, struct network_peer *peer) +{ + struct sockaddr_in6 sin6 = {}; + struct pex_hdr *hdr = (struct pex_hdr *)tx_buf; + size_t tx_len = sizeof(*hdr) + hdr->len; + int ret; + + if (peer == &net->net_config.local_host->peer || !peer->state.connected) + return; + + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &peer->local_addr.in6, + sizeof(peer->local_addr.in6)); + sin6.sin6_port = htons(net->net_config.pex_port); + hdr->len = htons(hdr->len); + ret = sendto(net->pex.fd.fd, tx_buf, tx_len, 0, (struct sockaddr *)&sin6, sizeof(sin6)); + hdr->len = ntohs(hdr->len); + if (ret < 0) + D_PEER(net, peer, "pex_msg_send failed: %s", strerror(errno)); +} + +static void +pex_send_hello(struct network *net, struct network_peer *peer) +{ + struct pex_hello *data; + + pex_msg_init(net, PEX_MSG_HELLO); + data = pex_msg_append(sizeof(*data)); + if (peer->state.endpoint.sa.sa_family == AF_INET6) + data->flags |= htons(PEER_EP_F_IPV6); + if (network_get_local_addr(&data->local_addr, &peer->state.endpoint)) + return; + + pex_msg_send(net, peer); +} + + +static int +pex_msg_add_peer_endpoint(struct network *net, struct network_peer *peer, + struct network_peer *receiver) +{ + struct pex_peer_endpoint *data; + uint16_t flags = 0; + const void *addr; + int port; + int len; + + addr = network_endpoint_addr(&peer->state.endpoint, &len); + port = peer->state.endpoint.in.sin_port; + if (len > 4) + flags |= PEER_EP_F_IPV6; + if (network_endpoint_addr_equal(&peer->state.endpoint, + &receiver->state.endpoint)) { + if (!peer->state.has_local_ep_addr) { + D_PEER(net, peer, "can't send peer to %s, missing local address", + network_peer_name(receiver)); + return -1; + } + + addr = &peer->state.local_ep_addr; + port = htons(peer->port); + flags |= PEER_EP_F_LOCAL; + } + + data = pex_msg_append(sizeof(*data)); + if (!data) + return -1; + + memcpy(data->peer_id, peer->key, sizeof(data->peer_id)); + memcpy(data->addr, addr, len); + data->port = port; + data->flags = htons(flags); + D_PEER(net, peer, "send endpoint to %s", network_peer_name(receiver)); + + return 0; +} + +static void +network_pex_handle_endpoint_change(struct network *net, struct network_peer *peer) +{ + struct network_peer *cur; + + vlist_for_each_element(&net->peers, cur, node) { + if (cur == peer || !cur->state.connected) + continue; + + pex_msg_init(net, PEX_MSG_NOTIFY_PEERS); + if (pex_msg_add_peer_endpoint(net, peer, cur)) + continue; + + pex_msg_send(net, cur); + } +} + +void network_pex_init(struct network *net) +{ + struct network_pex *pex = &net->pex; + + memset(pex, 0, sizeof(*pex)); + pex->fd.fd = -1; +} + +static void +network_pex_query_hosts(struct network *net) +{ + struct network_host *host; + int rv = rand(); + int hosts = 0; + int i; + + pex_msg_init(net, PEX_MSG_QUERY); + + avl_for_each_element(&net->hosts, host, node) { + struct network_peer *peer = &host->peer; + void *id; + + if (host == net->net_config.local_host || + peer->state.connected || + peer->endpoint) + continue; + + id = pex_msg_append(PEX_ID_LEN); + if (!id) + break; + + memcpy(id, peer->key, PEX_ID_LEN); + hosts++; + } + + if (!hosts) + return; + + rv %= net->hosts.count; + for (i = 0; i < 2; i++) { + avl_for_each_element(&net->hosts, host, node) { + struct network_peer *peer = &host->peer; + + if (rv > 0) { + rv--; + continue; + } + + if (host == net->net_config.local_host) + continue; + + if (!peer->state.connected) + continue; + + D_PEER(net, peer, "send query for %d hosts", hosts); + pex_msg_send(net, peer); + return; + } + } + +} + +static void +network_pex_send_ping(struct network *net, struct network_peer *peer) +{ + pex_msg_init(net, PEX_MSG_PING); + pex_msg_send(net, peer); +} + +void network_pex_event(struct network *net, struct network_peer *peer, + enum pex_event ev) +{ + if (!network_pex_active(&net->pex)) + return; + + if (peer) + D_PEER(net, peer, "PEX event type=%d", ev); + else + D_NET(net, "PEX event type=%d", ev); + + switch (ev) { + case PEX_EV_HANDSHAKE: + pex_send_hello(net, peer); + break; + case PEX_EV_ENDPOINT_CHANGE: + network_pex_handle_endpoint_change(net, peer); + break; + case PEX_EV_QUERY: + network_pex_query_hosts(net); + break; + case PEX_EV_PING: + network_pex_send_ping(net, peer); + break; + } +} + +static void +network_pex_recv_hello(struct network *net, struct network_peer *peer, + const struct pex_hello *data, size_t len) +{ + char addrstr[INET6_ADDRSTRLEN]; + uint16_t flags; + int af; + + if (len < sizeof(*data)) + return; + + if (peer->state.has_local_ep_addr && + !memcmp(&peer->state.local_ep_addr, data->local_addr, sizeof(data->local_addr))) + return; + + flags = ntohs(data->flags); + af = (flags & PEER_EP_F_IPV6) ? AF_INET6 : AF_INET; + D_PEER(net, peer, "set local endpoint address to %s", + inet_ntop(af, data->local_addr, addrstr, sizeof(addrstr))); + peer->state.has_local_ep_addr = true; + memcpy(&peer->state.local_ep_addr, data->local_addr, sizeof(data->local_addr)); +} + +static void +network_pex_recv_peers(struct network *net, struct network_peer *peer, + const struct pex_peer_endpoint *data, size_t len) +{ + struct network_peer *local = &net->net_config.local_host->peer; + struct network_peer *cur; + + for (; len >= sizeof(*data); len -= sizeof(*data), data++) { + union network_endpoint *ep; + uint16_t flags; + void *addr; + int len; + + cur = pex_msg_peer(net, data->peer_id); + if (!cur) + continue; + + if (cur == peer || cur == local) + continue; + + D_PEER(net, peer, "received peer address for %s\n", + network_peer_name(cur)); + flags = ntohs(data->flags); + ep = &cur->state.next_endpoint; + ep->sa.sa_family = (flags & PEER_EP_F_IPV6) ? AF_INET6 : AF_INET; + addr = network_endpoint_addr(ep, &len); + memcpy(addr, data->addr, len); + ep->in.sin_port = data->port; + } +} + +static void +network_pex_recv_query(struct network *net, struct network_peer *peer, + const uint8_t *data, size_t len) +{ + struct network_peer *cur; + int resp = 0; + + pex_msg_init(net, PEX_MSG_NOTIFY_PEERS); + for (; len >= 8; data += 8, len -= 8) { + cur = pex_msg_peer(net, data); + if (!cur || !cur->state.connected) + continue; + + if (!pex_msg_add_peer_endpoint(net, cur, peer)) + resp++; + } + + if (!resp) + return; + + D_PEER(net, peer, "send query response with %d hosts", resp); + pex_msg_send(net, peer); +} + +static void +network_pex_recv_ping(struct network *net, struct network_peer *peer) +{ + time_t now = time(NULL); + + if (peer->state.last_request == now) + return; + + peer->state.last_request = now; + pex_msg_init(net, PEX_MSG_PONG); + pex_msg_send(net, peer); +} + +static void +network_pex_recv(struct network *net, struct network_peer *peer, struct pex_hdr *hdr) +{ + const void *data = hdr + 1; + + if (hdr->version != 0) + return; + + D_PEER(net, peer, "PEX rx op=%d", hdr->opcode); + switch (hdr->opcode) { + case PEX_MSG_HELLO: + network_pex_recv_hello(net, peer, data, hdr->len); + break; + case PEX_MSG_NOTIFY_PEERS: + network_pex_recv_peers(net, peer, data, hdr->len); + break; + case PEX_MSG_QUERY: + network_pex_recv_query(net, peer, data, hdr->len); + break; + case PEX_MSG_PING: + network_pex_recv_ping(net, peer); + break; + case PEX_MSG_PONG: + break; + } +} + +static void +network_pex_fd_cb(struct uloop_fd *fd, unsigned int events) +{ + struct network *net = container_of(fd, struct network, pex.fd); + struct network_peer *local = &net->net_config.local_host->peer; + struct network_peer *peer; + struct sockaddr_in6 sin6; + static char buf[PEX_BUF_SIZE]; + struct pex_hdr *hdr = (struct pex_hdr *)buf; + ssize_t len; + + while (1) { + socklen_t slen = sizeof(sin6); + + len = recvfrom(fd->fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6, &slen); + if (len < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + break; + + D_NET(net, "recvfrom failed: %s", strerror(errno)); + network_pex_close(net); + return; + } + + if (!len) + continue; + + if (len < sizeof(*hdr)) + continue; + + hdr->len = ntohs(hdr->len); + if (len - sizeof(hdr) < hdr->len) + continue; + + peer = pex_msg_peer(net, hdr->id); + if (!peer) + continue; + + if (memcmp(&sin6.sin6_addr, &peer->local_addr.in6, sizeof(sin6.sin6_addr)) != 0) + continue; + + if (peer == local) + continue; + + network_pex_recv(net, peer, hdr); + } +} + +int network_pex_open(struct network *net) +{ + struct network_peer *local = &net->net_config.local_host->peer; + struct network_pex *pex = &net->pex; + struct sockaddr_in6 sin6 = {}; + int yes = 1; + int fd; + + if (dummy_mode || !local || !net->net_config.pex_port) + return 0; + + fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &local->local_addr.in6, + sizeof(local->local_addr.in6)); + sin6.sin6_port = htons(net->net_config.pex_port); + + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) { + perror("bind"); + goto close; + } + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); +#ifdef linux + setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + network_name(net), strlen(network_name(net))); +#endif + + pex->fd.fd = fd; + pex->fd.cb = network_pex_fd_cb; + uloop_fd_add(&pex->fd, ULOOP_READ); + + return 0; + +close: + close(fd); + return -1; +} + +void network_pex_close(struct network *net) +{ + struct network_pex *pex = &net->pex; + + if (pex->fd.fd < 0) + return; + + uloop_fd_delete(&pex->fd); + close(pex->fd.fd); + network_pex_init(net); +} diff --git a/pex.h b/pex.h new file mode 100644 index 0000000..8ecf938 --- /dev/null +++ b/pex.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_PEX_H +#define __UNETD_PEX_H + +#include + +struct network; + +struct network_pex { + struct uloop_fd fd; +}; + +enum pex_event { + PEX_EV_HANDSHAKE, + PEX_EV_ENDPOINT_CHANGE, + PEX_EV_QUERY, + PEX_EV_PING, +}; + +void network_pex_init(struct network *net); +int network_pex_open(struct network *net); +void network_pex_close(struct network *net); + +void network_pex_event(struct network *net, struct network_peer *peer, + enum pex_event ev); + +static inline bool network_pex_active(struct network_pex *pex) +{ + return pex->fd.fd >= 0; +} + +#endif diff --git a/scripts/json_pp.pm b/scripts/json_pp.pm new file mode 100644 index 0000000..d8b7ab3 --- /dev/null +++ b/scripts/json_pp.pm @@ -0,0 +1,3147 @@ +package JSON::PP; + +# JSON-2.0 + +use 5.005; +use strict; + +use Exporter (); +BEGIN { @JSON::PP::ISA = ('Exporter') } + +use overload (); +use JSON::PP::Boolean; + +use Carp (); +#use Devel::Peek; + +$JSON::PP::VERSION = '4.02'; + +@JSON::PP::EXPORT = qw(encode_json decode_json from_json to_json); + +# instead of hash-access, i tried index-access for speed. +# but this method is not faster than what i expected. so it will be changed. + +use constant P_ASCII => 0; +use constant P_LATIN1 => 1; +use constant P_UTF8 => 2; +use constant P_INDENT => 3; +use constant P_CANONICAL => 4; +use constant P_SPACE_BEFORE => 5; +use constant P_SPACE_AFTER => 6; +use constant P_ALLOW_NONREF => 7; +use constant P_SHRINK => 8; +use constant P_ALLOW_BLESSED => 9; +use constant P_CONVERT_BLESSED => 10; +use constant P_RELAXED => 11; + +use constant P_LOOSE => 12; +use constant P_ALLOW_BIGNUM => 13; +use constant P_ALLOW_BAREKEY => 14; +use constant P_ALLOW_SINGLEQUOTE => 15; +use constant P_ESCAPE_SLASH => 16; +use constant P_AS_NONBLESSED => 17; + +use constant P_ALLOW_UNKNOWN => 18; +use constant P_ALLOW_TAGS => 19; + +use constant OLD_PERL => $] < 5.008 ? 1 : 0; +use constant USE_B => $ENV{PERL_JSON_PP_USE_B} || 0; + +BEGIN { + if (USE_B) { + require B; + } +} + +BEGIN { + my @xs_compati_bit_properties = qw( + latin1 ascii utf8 indent canonical space_before space_after allow_nonref shrink + allow_blessed convert_blessed relaxed allow_unknown + allow_tags + ); + my @pp_bit_properties = qw( + allow_singlequote allow_bignum loose + allow_barekey escape_slash as_nonblessed + ); + + # Perl version check, Unicode handling is enabled? + # Helper module sets @JSON::PP::_properties. + if ( OLD_PERL ) { + my $helper = $] >= 5.006 ? 'JSON::PP::Compat5006' : 'JSON::PP::Compat5005'; + eval qq| require $helper |; + if ($@) { Carp::croak $@; } + } + + for my $name (@xs_compati_bit_properties, @pp_bit_properties) { + my $property_id = 'P_' . uc($name); + + eval qq/ + sub $name { + my \$enable = defined \$_[1] ? \$_[1] : 1; + + if (\$enable) { + \$_[0]->{PROPS}->[$property_id] = 1; + } + else { + \$_[0]->{PROPS}->[$property_id] = 0; + } + + \$_[0]; + } + + sub get_$name { + \$_[0]->{PROPS}->[$property_id] ? 1 : ''; + } + /; + } + +} + + + +# Functions + +my $JSON; # cache + +sub encode_json ($) { # encode + ($JSON ||= __PACKAGE__->new->utf8)->encode(@_); +} + + +sub decode_json { # decode + ($JSON ||= __PACKAGE__->new->utf8)->decode(@_); +} + +# Obsoleted + +sub to_json($) { + Carp::croak ("JSON::PP::to_json has been renamed to encode_json."); +} + + +sub from_json($) { + Carp::croak ("JSON::PP::from_json has been renamed to decode_json."); +} + + +# Methods + +sub new { + my $class = shift; + my $self = { + max_depth => 512, + max_size => 0, + indent_length => 3, + }; + + $self->{PROPS}[P_ALLOW_NONREF] = 1; + + bless $self, $class; +} + + +sub encode { + return $_[0]->PP_encode_json($_[1]); +} + + +sub decode { + return $_[0]->PP_decode_json($_[1], 0x00000000); +} + + +sub decode_prefix { + return $_[0]->PP_decode_json($_[1], 0x00000001); +} + + +# accessor + + +# pretty printing + +sub pretty { + my ($self, $v) = @_; + my $enable = defined $v ? $v : 1; + + if ($enable) { # indent_length(3) for JSON::XS compatibility + $self->indent(1)->space_before(1)->space_after(1); + } + else { + $self->indent(0)->space_before(0)->space_after(0); + } + + $self; +} + +# etc + +sub max_depth { + my $max = defined $_[1] ? $_[1] : 0x80000000; + $_[0]->{max_depth} = $max; + $_[0]; +} + + +sub get_max_depth { $_[0]->{max_depth}; } + + +sub max_size { + my $max = defined $_[1] ? $_[1] : 0; + $_[0]->{max_size} = $max; + $_[0]; +} + + +sub get_max_size { $_[0]->{max_size}; } + +sub boolean_values { + my $self = shift; + if (@_) { + my ($false, $true) = @_; + $self->{false} = $false; + $self->{true} = $true; + return ($false, $true); + } else { + delete $self->{false}; + delete $self->{true}; + return; + } +} + +sub get_boolean_values { + my $self = shift; + if (exists $self->{true} and exists $self->{false}) { + return @$self{qw/false true/}; + } + return; +} + +sub filter_json_object { + if (defined $_[1] and ref $_[1] eq 'CODE') { + $_[0]->{cb_object} = $_[1]; + } else { + delete $_[0]->{cb_object}; + } + $_[0]->{F_HOOK} = ($_[0]->{cb_object} or $_[0]->{cb_sk_object}) ? 1 : 0; + $_[0]; +} + +sub filter_json_single_key_object { + if (@_ == 1 or @_ > 3) { + Carp::croak("Usage: JSON::PP::filter_json_single_key_object(self, key, callback = undef)"); + } + if (defined $_[2] and ref $_[2] eq 'CODE') { + $_[0]->{cb_sk_object}->{$_[1]} = $_[2]; + } else { + delete $_[0]->{cb_sk_object}->{$_[1]}; + delete $_[0]->{cb_sk_object} unless %{$_[0]->{cb_sk_object} || {}}; + } + $_[0]->{F_HOOK} = ($_[0]->{cb_object} or $_[0]->{cb_sk_object}) ? 1 : 0; + $_[0]; +} + +sub indent_length { + if (!defined $_[1] or $_[1] > 15 or $_[1] < 0) { + Carp::carp "The acceptable range of indent_length() is 0 to 15."; + } + else { + $_[0]->{indent_length} = $_[1]; + } + $_[0]; +} + +sub get_indent_length { + $_[0]->{indent_length}; +} + +sub sort_by { + $_[0]->{sort_by} = defined $_[1] ? $_[1] : 1; + $_[0]; +} + +sub allow_bigint { + Carp::carp("allow_bigint() is obsoleted. use allow_bignum() instead."); + $_[0]->allow_bignum; +} + +############################### + +### +### Perl => JSON +### + + +{ # Convert + + my $max_depth; + my $indent; + my $ascii; + my $latin1; + my $utf8; + my $space_before; + my $space_after; + my $canonical; + my $allow_blessed; + my $convert_blessed; + + my $indent_length; + my $escape_slash; + my $bignum; + my $as_nonblessed; + my $allow_tags; + + my $depth; + my $indent_count; + my $keysort; + + + sub PP_encode_json { + my $self = shift; + my $obj = shift; + + $indent_count = 0; + $depth = 0; + + my $props = $self->{PROPS}; + + ($ascii, $latin1, $utf8, $indent, $canonical, $space_before, $space_after, $allow_blessed, + $convert_blessed, $escape_slash, $bignum, $as_nonblessed, $allow_tags) + = @{$props}[P_ASCII .. P_SPACE_AFTER, P_ALLOW_BLESSED, P_CONVERT_BLESSED, + P_ESCAPE_SLASH, P_ALLOW_BIGNUM, P_AS_NONBLESSED, P_ALLOW_TAGS]; + + ($max_depth, $indent_length) = @{$self}{qw/max_depth indent_length/}; + + $keysort = $canonical ? sub { $a cmp $b } : undef; + + if ($self->{sort_by}) { + $keysort = ref($self->{sort_by}) eq 'CODE' ? $self->{sort_by} + : $self->{sort_by} =~ /\D+/ ? $self->{sort_by} + : sub { $a cmp $b }; + } + + encode_error("hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this)") + if(!ref $obj and !$props->[ P_ALLOW_NONREF ]); + + my $str = $self->object_to_json($obj); + + $str .= "\n" if ( $indent ); # JSON::XS 2.26 compatible + + unless ($ascii or $latin1 or $utf8) { + utf8::upgrade($str); + } + + if ($props->[ P_SHRINK ]) { + utf8::downgrade($str, 1); + } + + return $str; + } + + + sub object_to_json { + my ($self, $obj) = @_; + my $type = ref($obj); + + if($type eq 'HASH'){ + return $self->hash_to_json($obj); + } + elsif($type eq 'ARRAY'){ + return $self->array_to_json($obj); + } + elsif ($type) { # blessed object? + if (blessed($obj)) { + + return $self->value_to_json($obj) if ( $obj->isa('JSON::PP::Boolean') ); + + if ( $allow_tags and $obj->can('FREEZE') ) { + my $obj_class = ref $obj || $obj; + $obj = bless $obj, $obj_class; + my @results = $obj->FREEZE('JSON'); + if ( @results and ref $results[0] ) { + if ( refaddr( $obj ) eq refaddr( $results[0] ) ) { + encode_error( sprintf( + "%s::FREEZE method returned same object as was passed instead of a new one", + ref $obj + ) ); + } + } + return '("'.$obj_class.'")['.join(',', @results).']'; + } + + if ( $convert_blessed and $obj->can('TO_JSON') ) { + my $result = $obj->TO_JSON(); + if ( defined $result and ref( $result ) ) { + if ( refaddr( $obj ) eq refaddr( $result ) ) { + encode_error( sprintf( + "%s::TO_JSON method returned same object as was passed instead of a new one", + ref $obj + ) ); + } + } + + return $self->object_to_json( $result ); + } + + return "$obj" if ( $bignum and _is_bignum($obj) ); + + if ($allow_blessed) { + return $self->blessed_to_json($obj) if ($as_nonblessed); # will be removed. + return 'null'; + } + encode_error( sprintf("encountered object '%s', but neither allow_blessed, convert_blessed nor allow_tags settings are enabled (or TO_JSON/FREEZE method missing)", $obj) + ); + } + else { + return $self->value_to_json($obj); + } + } + else{ + return $self->value_to_json($obj); + } + } + + + sub hash_to_json { + my ($self, $obj) = @_; + my @res; + + encode_error("json text or perl structure exceeds maximum nesting level (max_depth set too low?)") + if (++$depth > $max_depth); + + my ($pre, $post) = $indent ? $self->_up_indent() : ('', ''); + my $del = ($space_before ? ' ' : '') . ':' . ($space_after ? ' ' : ''); + + for my $k ( _sort( $obj ) ) { + if ( OLD_PERL ) { utf8::decode($k) } # key for Perl 5.6 / be optimized + push @res, $self->string_to_json( $k ) + . $del + . ( ref $obj->{$k} ? $self->object_to_json( $obj->{$k} ) : $self->value_to_json( $obj->{$k} ) ); + } + + --$depth; + $self->_down_indent() if ($indent); + + return '{}' unless @res; + return '{' . $pre . join( ",$pre", @res ) . $post . '}'; + } + + + sub array_to_json { + my ($self, $obj) = @_; + my @res; + + encode_error("json text or perl structure exceeds maximum nesting level (max_depth set too low?)") + if (++$depth > $max_depth); + + my ($pre, $post) = $indent ? $self->_up_indent() : ('', ''); + + for my $v (@$obj){ + push @res, ref($v) ? $self->object_to_json($v) : $self->value_to_json($v); + } + + --$depth; + $self->_down_indent() if ($indent); + + return '[]' unless @res; + return '[' . $pre . join( ",$pre", @res ) . $post . ']'; + } + + sub _looks_like_number { + my $value = shift; + if (USE_B) { + my $b_obj = B::svref_2object(\$value); + my $flags = $b_obj->FLAGS; + return 1 if $flags & ( B::SVp_IOK() | B::SVp_NOK() ) and !( $flags & B::SVp_POK() ); + return; + } else { + no warnings 'numeric'; + # if the utf8 flag is on, it almost certainly started as a string + return if utf8::is_utf8($value); + # detect numbers + # string & "" -> "" + # number & "" -> 0 (with warning) + # nan and inf can detect as numbers, so check with * 0 + return unless length((my $dummy = "") & $value); + return unless 0 + $value eq $value; + return 1 if $value * 0 == 0; + return -1; # inf/nan + } + } + + sub value_to_json { + my ($self, $value) = @_; + + return 'null' if(!defined $value); + + my $type = ref($value); + + if (!$type) { + if (_looks_like_number($value)) { + return $value; + } + return $self->string_to_json($value); + } + elsif( blessed($value) and $value->isa('JSON::PP::Boolean') ){ + return $$value == 1 ? 'true' : 'false'; + } + else { + if ((overload::StrVal($value) =~ /=(\w+)/)[0]) { + return $self->value_to_json("$value"); + } + + if ($type eq 'SCALAR' and defined $$value) { + return $$value eq '1' ? 'true' + : $$value eq '0' ? 'false' + : $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ? 'null' + : encode_error("cannot encode reference to scalar"); + } + + if ( $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ) { + return 'null'; + } + else { + if ( $type eq 'SCALAR' or $type eq 'REF' ) { + encode_error("cannot encode reference to scalar"); + } + else { + encode_error("encountered $value, but JSON can only represent references to arrays or hashes"); + } + } + + } + } + + + my %esc = ( + "\n" => '\n', + "\r" => '\r', + "\t" => '\t', + "\f" => '\f', + "\b" => '\b', + "\"" => '\"', + "\\" => '\\\\', + "\'" => '\\\'', + ); + + + sub string_to_json { + my ($self, $arg) = @_; + + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; + $arg =~ s/\//\\\//g if ($escape_slash); + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; + + if ($ascii) { + $arg = JSON_PP_encode_ascii($arg); + } + + if ($latin1) { + $arg = JSON_PP_encode_latin1($arg); + } + + if ($utf8) { + utf8::encode($arg); + } + + return '"' . $arg . '"'; + } + + + sub blessed_to_json { + my $reftype = reftype($_[1]) || ''; + if ($reftype eq 'HASH') { + return $_[0]->hash_to_json($_[1]); + } + elsif ($reftype eq 'ARRAY') { + return $_[0]->array_to_json($_[1]); + } + else { + return 'null'; + } + } + + + sub encode_error { + my $error = shift; + Carp::croak "$error"; + } + + + sub _sort { + defined $keysort ? (sort $keysort (keys %{$_[0]})) : keys %{$_[0]}; + } + + + sub _up_indent { + my $self = shift; + my $space = ' ' x $indent_length; + + my ($pre,$post) = ('',''); + + $post = "\n" . $space x $indent_count; + + $indent_count++; + + $pre = "\n" . $space x $indent_count; + + return ($pre,$post); + } + + + sub _down_indent { $indent_count--; } + + + sub PP_encode_box { + { + depth => $depth, + indent_count => $indent_count, + }; + } + +} # Convert + + +sub _encode_ascii { + join('', + map { + $_ <= 127 ? + chr($_) : + $_ <= 65535 ? + sprintf('\u%04x', $_) : sprintf('\u%x\u%x', _encode_surrogates($_)); + } unpack('U*', $_[0]) + ); +} + + +sub _encode_latin1 { + join('', + map { + $_ <= 255 ? + chr($_) : + $_ <= 65535 ? + sprintf('\u%04x', $_) : sprintf('\u%x\u%x', _encode_surrogates($_)); + } unpack('U*', $_[0]) + ); +} + + +sub _encode_surrogates { # from perlunicode + my $uni = $_[0] - 0x10000; + return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00); +} + + +sub _is_bignum { + $_[0]->isa('Math::BigInt') or $_[0]->isa('Math::BigFloat'); +} + + + +# +# JSON => Perl +# + +my $max_intsize; + +BEGIN { + my $checkint = 1111; + for my $d (5..64) { + $checkint .= 1; + my $int = eval qq| $checkint |; + if ($int =~ /[eE]/) { + $max_intsize = $d - 1; + last; + } + } +} + +{ # PARSE + + my %escapes = ( # by Jeremy Muhlich + b => "\x8", + t => "\x9", + n => "\xA", + f => "\xC", + r => "\xD", + '\\' => '\\', + '"' => '"', + '/' => '/', + ); + + my $text; # json data + my $at; # offset + my $ch; # first character + my $len; # text length (changed according to UTF8 or NON UTF8) + # INTERNAL + my $depth; # nest counter + my $encoding; # json text encoding + my $is_valid_utf8; # temp variable + my $utf8_len; # utf8 byte length + # FLAGS + my $utf8; # must be utf8 + my $max_depth; # max nest number of objects and arrays + my $max_size; + my $relaxed; + my $cb_object; + my $cb_sk_object; + + my $F_HOOK; + + my $allow_bignum; # using Math::BigInt/BigFloat + my $singlequote; # loosely quoting + my $loose; # + my $allow_barekey; # bareKey + my $allow_tags; + + my $alt_true; + my $alt_false; + + sub _detect_utf_encoding { + my $text = shift; + my @octets = unpack('C4', $text); + return 'unknown' unless defined $octets[3]; + return ( $octets[0] and $octets[1]) ? 'UTF-8' + : (!$octets[0] and $octets[1]) ? 'UTF-16BE' + : (!$octets[0] and !$octets[1]) ? 'UTF-32BE' + : ( $octets[2] ) ? 'UTF-16LE' + : (!$octets[2] ) ? 'UTF-32LE' + : 'unknown'; + } + + sub PP_decode_json { + my ($self, $want_offset); + + ($self, $text, $want_offset) = @_; + + ($at, $ch, $depth) = (0, '', 0); + + if ( !defined $text or ref $text ) { + decode_error("malformed JSON string, neither array, object, number, string or atom"); + } + + my $props = $self->{PROPS}; + + ($utf8, $relaxed, $loose, $allow_bignum, $allow_barekey, $singlequote, $allow_tags) + = @{$props}[P_UTF8, P_RELAXED, P_LOOSE .. P_ALLOW_SINGLEQUOTE, P_ALLOW_TAGS]; + + ($alt_true, $alt_false) = @$self{qw/true false/}; + + if ( $utf8 ) { + $encoding = _detect_utf_encoding($text); + if ($encoding ne 'UTF-8' and $encoding ne 'unknown') { + require Encode; + Encode::from_to($text, $encoding, 'utf-8'); + } else { + utf8::downgrade( $text, 1 ) or Carp::croak("Wide character in subroutine entry"); + } + } + else { + utf8::upgrade( $text ); + utf8::encode( $text ); + } + + $len = length $text; + + ($max_depth, $max_size, $cb_object, $cb_sk_object, $F_HOOK) + = @{$self}{qw/max_depth max_size cb_object cb_sk_object F_HOOK/}; + + if ($max_size > 1) { + use bytes; + my $bytes = length $text; + decode_error( + sprintf("attempted decode of JSON text of %s bytes size, but max_size is set to %s" + , $bytes, $max_size), 1 + ) if ($bytes > $max_size); + } + + white(); # remove head white space + + decode_error("malformed JSON string, neither array, object, number, string or atom") unless defined $ch; # Is there a first character for JSON structure? + + my $result = value(); + + if ( !$props->[ P_ALLOW_NONREF ] and !ref $result ) { + decode_error( + 'JSON text must be an object or array (but found number, string, true, false or null,' + . ' use allow_nonref to allow this)', 1); + } + + Carp::croak('something wrong.') if $len < $at; # we won't arrive here. + + my $consumed = defined $ch ? $at - 1 : $at; # consumed JSON text length + + white(); # remove tail white space + + return ( $result, $consumed ) if $want_offset; # all right if decode_prefix + + decode_error("garbage after JSON object") if defined $ch; + + $result; + } + + + sub next_chr { + return $ch = undef if($at >= $len); + $ch = substr($text, $at++, 1); + } + + + sub value { + white(); + return if(!defined $ch); + return object() if($ch eq '{'); + return array() if($ch eq '['); + return tag() if($ch eq '('); + return string() if($ch eq '"' or ($singlequote and $ch eq "'")); + return number() if($ch =~ /[0-9]/ or $ch eq '-'); + return word(); + } + + sub string { + my $utf16; + my $is_utf8; + + ($is_valid_utf8, $utf8_len) = ('', 0); + + my $s = ''; # basically UTF8 flag on + + if($ch eq '"' or ($singlequote and $ch eq "'")){ + my $boundChar = $ch; + + OUTER: while( defined(next_chr()) ){ + + if($ch eq $boundChar){ + next_chr(); + + if ($utf16) { + decode_error("missing low surrogate character in surrogate pair"); + } + + utf8::decode($s) if($is_utf8); + + return $s; + } + elsif($ch eq '\\'){ + next_chr(); + if(exists $escapes{$ch}){ + $s .= $escapes{$ch}; + } + elsif($ch eq 'u'){ # UNICODE handling + my $u = ''; + + for(1..4){ + $ch = next_chr(); + last OUTER if($ch !~ /[0-9a-fA-F]/); + $u .= $ch; + } + + # U+D800 - U+DBFF + if ($u =~ /^[dD][89abAB][0-9a-fA-F]{2}/) { # UTF-16 high surrogate? + $utf16 = $u; + } + # U+DC00 - U+DFFF + elsif ($u =~ /^[dD][c-fC-F][0-9a-fA-F]{2}/) { # UTF-16 low surrogate? + unless (defined $utf16) { + decode_error("missing high surrogate character in surrogate pair"); + } + $is_utf8 = 1; + $s .= JSON_PP_decode_surrogates($utf16, $u) || next; + $utf16 = undef; + } + else { + if (defined $utf16) { + decode_error("surrogate pair expected"); + } + + if ( ( my $hex = hex( $u ) ) > 127 ) { + $is_utf8 = 1; + $s .= JSON_PP_decode_unicode($u) || next; + } + else { + $s .= chr $hex; + } + } + + } + else{ + unless ($loose) { + $at -= 2; + decode_error('illegal backslash escape sequence in string'); + } + $s .= $ch; + } + } + else{ + + if ( ord $ch > 127 ) { + unless( $ch = is_valid_utf8($ch) ) { + $at -= 1; + decode_error("malformed UTF-8 character in JSON string"); + } + else { + $at += $utf8_len - 1; + } + + $is_utf8 = 1; + } + + if (!$loose) { + if ($ch =~ /[\x00-\x1f\x22\x5c]/) { # '/' ok + if (!$relaxed or $ch ne "\t") { + $at--; + decode_error('invalid character encountered while parsing JSON string'); + } + } + } + + $s .= $ch; + } + } + } + + decode_error("unexpected end of string while parsing JSON string"); + } + + + sub white { + while( defined $ch ){ + if($ch eq '' or $ch =~ /\A[ \t\r\n]\z/){ + next_chr(); + } + elsif($relaxed and $ch eq '/'){ + next_chr(); + if(defined $ch and $ch eq '/'){ + 1 while(defined(next_chr()) and $ch ne "\n" and $ch ne "\r"); + } + elsif(defined $ch and $ch eq '*'){ + next_chr(); + while(1){ + if(defined $ch){ + if($ch eq '*'){ + if(defined(next_chr()) and $ch eq '/'){ + next_chr(); + last; + } + } + else{ + next_chr(); + } + } + else{ + decode_error("Unterminated comment"); + } + } + next; + } + else{ + $at--; + decode_error("malformed JSON string, neither array, object, number, string or atom"); + } + } + else{ + if ($relaxed and $ch eq '#') { # correctly? + pos($text) = $at; + $text =~ /\G([^\n]*(?:\r\n|\r|\n|$))/g; + $at = pos($text); + next_chr; + next; + } + + last; + } + } + } + + + sub array { + my $a = $_[0] || []; # you can use this code to use another array ref object. + + decode_error('json text or perl structure exceeds maximum nesting level (max_depth set too low?)') + if (++$depth > $max_depth); + + next_chr(); + white(); + + if(defined $ch and $ch eq ']'){ + --$depth; + next_chr(); + return $a; + } + else { + while(defined($ch)){ + push @$a, value(); + + white(); + + if (!defined $ch) { + last; + } + + if($ch eq ']'){ + --$depth; + next_chr(); + return $a; + } + + if($ch ne ','){ + last; + } + + next_chr(); + white(); + + if ($relaxed and $ch eq ']') { + --$depth; + next_chr(); + return $a; + } + + } + } + + $at-- if defined $ch and $ch ne ''; + decode_error(", or ] expected while parsing array"); + } + + sub tag { + decode_error('malformed JSON string, neither array, object, number, string or atom') unless $allow_tags; + + next_chr(); + white(); + + my $tag = value(); + return unless defined $tag; + decode_error('malformed JSON string, (tag) must be a string') if ref $tag; + + white(); + + if (!defined $ch or $ch ne ')') { + decode_error(') expected after tag'); + } + + next_chr(); + white(); + + my $val = value(); + return unless defined $val; + decode_error('malformed JSON string, tag value must be an array') unless ref $val eq 'ARRAY'; + + if (!eval { $tag->can('THAW') }) { + decode_error('cannot decode perl-object (package does not exist)') if $@; + decode_error('cannot decode perl-object (package does not have a THAW method)'); + } + $tag->THAW('JSON', @$val); + } + + sub object { + my $o = $_[0] || {}; # you can use this code to use another hash ref object. + my $k; + + decode_error('json text or perl structure exceeds maximum nesting level (max_depth set too low?)') + if (++$depth > $max_depth); + next_chr(); + white(); + + if(defined $ch and $ch eq '}'){ + --$depth; + next_chr(); + if ($F_HOOK) { + return _json_object_hook($o); + } + return $o; + } + else { + while (defined $ch) { + $k = ($allow_barekey and $ch ne '"' and $ch ne "'") ? bareKey() : string(); + white(); + + if(!defined $ch or $ch ne ':'){ + $at--; + decode_error("':' expected"); + } + + next_chr(); + $o->{$k} = value(); + white(); + + last if (!defined $ch); + + if($ch eq '}'){ + --$depth; + next_chr(); + if ($F_HOOK) { + return _json_object_hook($o); + } + return $o; + } + + if($ch ne ','){ + last; + } + + next_chr(); + white(); + + if ($relaxed and $ch eq '}') { + --$depth; + next_chr(); + if ($F_HOOK) { + return _json_object_hook($o); + } + return $o; + } + + } + + } + + $at-- if defined $ch and $ch ne ''; + decode_error(", or } expected while parsing object/hash"); + } + + + sub bareKey { # doesn't strictly follow Standard ECMA-262 3rd Edition + my $key; + while($ch =~ /[^\x00-\x23\x25-\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]/){ + $key .= $ch; + next_chr(); + } + return $key; + } + + + sub word { + my $word = substr($text,$at-1,4); + + if($word eq 'true'){ + $at += 3; + next_chr; + return defined $alt_true ? $alt_true : $JSON::PP::true; + } + elsif($word eq 'null'){ + $at += 3; + next_chr; + return undef; + } + elsif($word eq 'fals'){ + $at += 3; + if(substr($text,$at,1) eq 'e'){ + $at++; + next_chr; + return defined $alt_false ? $alt_false : $JSON::PP::false; + } + } + + $at--; # for decode_error report + + decode_error("'null' expected") if ($word =~ /^n/); + decode_error("'true' expected") if ($word =~ /^t/); + decode_error("'false' expected") if ($word =~ /^f/); + decode_error("malformed JSON string, neither array, object, number, string or atom"); + } + + + sub number { + my $n = ''; + my $v; + my $is_dec; + my $is_exp; + + if($ch eq '-'){ + $n = '-'; + next_chr; + if (!defined $ch or $ch !~ /\d/) { + decode_error("malformed number (no digits after initial minus)"); + } + } + + # According to RFC4627, hex or oct digits are invalid. + if($ch eq '0'){ + my $peek = substr($text,$at,1); + if($peek =~ /^[0-9a-dfA-DF]/){ # e may be valid (exponential) + decode_error("malformed number (leading zero must not be followed by another digit)"); + } + $n .= $ch; + next_chr; + } + + while(defined $ch and $ch =~ /\d/){ + $n .= $ch; + next_chr; + } + + if(defined $ch and $ch eq '.'){ + $n .= '.'; + $is_dec = 1; + + next_chr; + if (!defined $ch or $ch !~ /\d/) { + decode_error("malformed number (no digits after decimal point)"); + } + else { + $n .= $ch; + } + + while(defined(next_chr) and $ch =~ /\d/){ + $n .= $ch; + } + } + + if(defined $ch and ($ch eq 'e' or $ch eq 'E')){ + $n .= $ch; + $is_exp = 1; + next_chr; + + if(defined($ch) and ($ch eq '+' or $ch eq '-')){ + $n .= $ch; + next_chr; + if (!defined $ch or $ch =~ /\D/) { + decode_error("malformed number (no digits after exp sign)"); + } + $n .= $ch; + } + elsif(defined($ch) and $ch =~ /\d/){ + $n .= $ch; + } + else { + decode_error("malformed number (no digits after exp sign)"); + } + + while(defined(next_chr) and $ch =~ /\d/){ + $n .= $ch; + } + + } + + $v .= $n; + + if ($is_dec or $is_exp) { + if ($allow_bignum) { + require Math::BigFloat; + return Math::BigFloat->new($v); + } + } else { + if (length $v > $max_intsize) { + if ($allow_bignum) { # from Adam Sussman + require Math::BigInt; + return Math::BigInt->new($v); + } + else { + return "$v"; + } + } + } + + return $is_dec ? $v/1.0 : 0+$v; + } + + + sub is_valid_utf8 { + + $utf8_len = $_[0] =~ /[\x00-\x7F]/ ? 1 + : $_[0] =~ /[\xC2-\xDF]/ ? 2 + : $_[0] =~ /[\xE0-\xEF]/ ? 3 + : $_[0] =~ /[\xF0-\xF4]/ ? 4 + : 0 + ; + + return unless $utf8_len; + + my $is_valid_utf8 = substr($text, $at - 1, $utf8_len); + + return ( $is_valid_utf8 =~ /^(?: + [\x00-\x7F] + |[\xC2-\xDF][\x80-\xBF] + |[\xE0][\xA0-\xBF][\x80-\xBF] + |[\xE1-\xEC][\x80-\xBF][\x80-\xBF] + |[\xED][\x80-\x9F][\x80-\xBF] + |[\xEE-\xEF][\x80-\xBF][\x80-\xBF] + |[\xF0][\x90-\xBF][\x80-\xBF][\x80-\xBF] + |[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF] + |[\xF4][\x80-\x8F][\x80-\xBF][\x80-\xBF] + )$/x ) ? $is_valid_utf8 : ''; + } + + + sub decode_error { + my $error = shift; + my $no_rep = shift; + my $str = defined $text ? substr($text, $at) : ''; + my $mess = ''; + my $type = 'U*'; + + if ( OLD_PERL ) { + my $type = $] < 5.006 ? 'C*' + : utf8::is_utf8( $str ) ? 'U*' # 5.6 + : 'C*' + ; + } + + for my $c ( unpack( $type, $str ) ) { # emulate pv_uni_display() ? + $mess .= $c == 0x07 ? '\a' + : $c == 0x09 ? '\t' + : $c == 0x0a ? '\n' + : $c == 0x0d ? '\r' + : $c == 0x0c ? '\f' + : $c < 0x20 ? sprintf('\x{%x}', $c) + : $c == 0x5c ? '\\\\' + : $c < 0x80 ? chr($c) + : sprintf('\x{%x}', $c) + ; + if ( length $mess >= 20 ) { + $mess .= '...'; + last; + } + } + + unless ( length $mess ) { + $mess = '(end of string)'; + } + + Carp::croak ( + $no_rep ? "$error" : "$error, at character offset $at (before \"$mess\")" + ); + + } + + + sub _json_object_hook { + my $o = $_[0]; + my @ks = keys %{$o}; + + if ( $cb_sk_object and @ks == 1 and exists $cb_sk_object->{ $ks[0] } and ref $cb_sk_object->{ $ks[0] } ) { + my @val = $cb_sk_object->{ $ks[0] }->( $o->{$ks[0]} ); + if (@val == 0) { + return $o; + } + elsif (@val == 1) { + return $val[0]; + } + else { + Carp::croak("filter_json_single_key_object callbacks must not return more than one scalar"); + } + } + + my @val = $cb_object->($o) if ($cb_object); + if (@val == 0) { + return $o; + } + elsif (@val == 1) { + return $val[0]; + } + else { + Carp::croak("filter_json_object callbacks must not return more than one scalar"); + } + } + + + sub PP_decode_box { + { + text => $text, + at => $at, + ch => $ch, + len => $len, + depth => $depth, + encoding => $encoding, + is_valid_utf8 => $is_valid_utf8, + }; + } + +} # PARSE + + +sub _decode_surrogates { # from perlunicode + my $uni = 0x10000 + (hex($_[0]) - 0xD800) * 0x400 + (hex($_[1]) - 0xDC00); + my $un = pack('U*', $uni); + utf8::encode( $un ); + return $un; +} + + +sub _decode_unicode { + my $un = pack('U', hex shift); + utf8::encode( $un ); + return $un; +} + +# +# Setup for various Perl versions (the code from JSON::PP58) +# + +BEGIN { + + unless ( defined &utf8::is_utf8 ) { + require Encode; + *utf8::is_utf8 = *Encode::is_utf8; + } + + if ( !OLD_PERL ) { + *JSON::PP::JSON_PP_encode_ascii = \&_encode_ascii; + *JSON::PP::JSON_PP_encode_latin1 = \&_encode_latin1; + *JSON::PP::JSON_PP_decode_surrogates = \&_decode_surrogates; + *JSON::PP::JSON_PP_decode_unicode = \&_decode_unicode; + + if ($] < 5.008003) { # join() in 5.8.0 - 5.8.2 is broken. + package JSON::PP; + require subs; + subs->import('join'); + eval q| + sub join { + return '' if (@_ < 2); + my $j = shift; + my $str = shift; + for (@_) { $str .= $j . $_; } + return $str; + } + |; + } + } + + + sub JSON::PP::incr_parse { + local $Carp::CarpLevel = 1; + ( $_[0]->{_incr_parser} ||= JSON::PP::IncrParser->new )->incr_parse( @_ ); + } + + + sub JSON::PP::incr_skip { + ( $_[0]->{_incr_parser} ||= JSON::PP::IncrParser->new )->incr_skip; + } + + + sub JSON::PP::incr_reset { + ( $_[0]->{_incr_parser} ||= JSON::PP::IncrParser->new )->incr_reset; + } + + eval q{ + sub JSON::PP::incr_text : lvalue { + $_[0]->{_incr_parser} ||= JSON::PP::IncrParser->new; + + if ( $_[0]->{_incr_parser}->{incr_pos} ) { + Carp::croak("incr_text cannot be called when the incremental parser already started parsing"); + } + $_[0]->{_incr_parser}->{incr_text}; + } + } if ( $] >= 5.006 ); + +} # Setup for various Perl versions (the code from JSON::PP58) + + +############################### +# Utilities +# + +BEGIN { + eval 'require Scalar::Util'; + unless($@){ + *JSON::PP::blessed = \&Scalar::Util::blessed; + *JSON::PP::reftype = \&Scalar::Util::reftype; + *JSON::PP::refaddr = \&Scalar::Util::refaddr; + } + else{ # This code is from Scalar::Util. + # warn $@; + eval 'sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) }'; + *JSON::PP::blessed = sub { + local($@, $SIG{__DIE__}, $SIG{__WARN__}); + ref($_[0]) ? eval { $_[0]->a_sub_not_likely_to_be_here } : undef; + }; + require B; + my %tmap = qw( + B::NULL SCALAR + B::HV HASH + B::AV ARRAY + B::CV CODE + B::IO IO + B::GV GLOB + B::REGEXP REGEXP + ); + *JSON::PP::reftype = sub { + my $r = shift; + + return undef unless length(ref($r)); + + my $t = ref(B::svref_2object($r)); + + return + exists $tmap{$t} ? $tmap{$t} + : length(ref($$r)) ? 'REF' + : 'SCALAR'; + }; + *JSON::PP::refaddr = sub { + return undef unless length(ref($_[0])); + + my $addr; + if(defined(my $pkg = blessed($_[0]))) { + $addr .= bless $_[0], 'Scalar::Util::Fake'; + bless $_[0], $pkg; + } + else { + $addr .= $_[0] + } + + $addr =~ /0x(\w+)/; + local $^W; + #no warnings 'portable'; + hex($1); + } + } +} + + +# shamelessly copied and modified from JSON::XS code. + +$JSON::PP::true = do { bless \(my $dummy = 1), "JSON::PP::Boolean" }; +$JSON::PP::false = do { bless \(my $dummy = 0), "JSON::PP::Boolean" }; + +sub is_bool { blessed $_[0] and ( $_[0]->isa("JSON::PP::Boolean") or $_[0]->isa("Types::Serialiser::BooleanBase") or $_[0]->isa("JSON::XS::Boolean") ); } + +sub true { $JSON::PP::true } +sub false { $JSON::PP::false } +sub null { undef; } + +############################### + +package JSON::PP::IncrParser; + +use strict; + +use constant INCR_M_WS => 0; # initial whitespace skipping +use constant INCR_M_STR => 1; # inside string +use constant INCR_M_BS => 2; # inside backslash +use constant INCR_M_JSON => 3; # outside anything, count nesting +use constant INCR_M_C0 => 4; +use constant INCR_M_C1 => 5; +use constant INCR_M_TFN => 6; +use constant INCR_M_NUM => 7; + +$JSON::PP::IncrParser::VERSION = '1.01'; + +sub new { + my ( $class ) = @_; + + bless { + incr_nest => 0, + incr_text => undef, + incr_pos => 0, + incr_mode => 0, + }, $class; +} + + +sub incr_parse { + my ( $self, $coder, $text ) = @_; + + $self->{incr_text} = '' unless ( defined $self->{incr_text} ); + + if ( defined $text ) { + if ( utf8::is_utf8( $text ) and !utf8::is_utf8( $self->{incr_text} ) ) { + utf8::upgrade( $self->{incr_text} ) ; + utf8::decode( $self->{incr_text} ) ; + } + $self->{incr_text} .= $text; + } + + if ( defined wantarray ) { + my $max_size = $coder->get_max_size; + my $p = $self->{incr_pos}; + my @ret; + { + do { + unless ( $self->{incr_nest} <= 0 and $self->{incr_mode} == INCR_M_JSON ) { + $self->_incr_parse( $coder ); + + if ( $max_size and $self->{incr_pos} > $max_size ) { + Carp::croak("attempted decode of JSON text of $self->{incr_pos} bytes size, but max_size is set to $max_size"); + } + unless ( $self->{incr_nest} <= 0 and $self->{incr_mode} == INCR_M_JSON ) { + # as an optimisation, do not accumulate white space in the incr buffer + if ( $self->{incr_mode} == INCR_M_WS and $self->{incr_pos} ) { + $self->{incr_pos} = 0; + $self->{incr_text} = ''; + } + last; + } + } + + my ($obj, $offset) = $coder->PP_decode_json( $self->{incr_text}, 0x00000001 ); + push @ret, $obj; + use bytes; + $self->{incr_text} = substr( $self->{incr_text}, $offset || 0 ); + $self->{incr_pos} = 0; + $self->{incr_nest} = 0; + $self->{incr_mode} = 0; + last unless wantarray; + } while ( wantarray ); + } + + if ( wantarray ) { + return @ret; + } + else { # in scalar context + return defined $ret[0] ? $ret[0] : undef; + } + } +} + + +sub _incr_parse { + my ($self, $coder) = @_; + my $text = $self->{incr_text}; + my $len = length $text; + my $p = $self->{incr_pos}; + +INCR_PARSE: + while ( $len > $p ) { + my $s = substr( $text, $p, 1 ); + last INCR_PARSE unless defined $s; + my $mode = $self->{incr_mode}; + + if ( $mode == INCR_M_WS ) { + while ( $len > $p ) { + $s = substr( $text, $p, 1 ); + last INCR_PARSE unless defined $s; + if ( ord($s) > 0x20 ) { + if ( $s eq '#' ) { + $self->{incr_mode} = INCR_M_C0; + redo INCR_PARSE; + } else { + $self->{incr_mode} = INCR_M_JSON; + redo INCR_PARSE; + } + } + $p++; + } + } elsif ( $mode == INCR_M_BS ) { + $p++; + $self->{incr_mode} = INCR_M_STR; + redo INCR_PARSE; + } elsif ( $mode == INCR_M_C0 or $mode == INCR_M_C1 ) { + while ( $len > $p ) { + $s = substr( $text, $p, 1 ); + last INCR_PARSE unless defined $s; + if ( $s eq "\n" ) { + $self->{incr_mode} = $self->{incr_mode} == INCR_M_C0 ? INCR_M_WS : INCR_M_JSON; + last; + } + $p++; + } + next; + } elsif ( $mode == INCR_M_TFN ) { + while ( $len > $p ) { + $s = substr( $text, $p++, 1 ); + next if defined $s and $s =~ /[rueals]/; + last; + } + $p--; + $self->{incr_mode} = INCR_M_JSON; + + last INCR_PARSE unless $self->{incr_nest}; + redo INCR_PARSE; + } elsif ( $mode == INCR_M_NUM ) { + while ( $len > $p ) { + $s = substr( $text, $p++, 1 ); + next if defined $s and $s =~ /[0-9eE.+\-]/; + last; + } + $p--; + $self->{incr_mode} = INCR_M_JSON; + + last INCR_PARSE unless $self->{incr_nest}; + redo INCR_PARSE; + } elsif ( $mode == INCR_M_STR ) { + while ( $len > $p ) { + $s = substr( $text, $p, 1 ); + last INCR_PARSE unless defined $s; + if ( $s eq '"' ) { + $p++; + $self->{incr_mode} = INCR_M_JSON; + + last INCR_PARSE unless $self->{incr_nest}; + redo INCR_PARSE; + } + elsif ( $s eq '\\' ) { + $p++; + if ( !defined substr($text, $p, 1) ) { + $self->{incr_mode} = INCR_M_BS; + last INCR_PARSE; + } + } + $p++; + } + } elsif ( $mode == INCR_M_JSON ) { + while ( $len > $p ) { + $s = substr( $text, $p++, 1 ); + if ( $s eq "\x00" ) { + $p--; + last INCR_PARSE; + } elsif ( $s eq "\x09" or $s eq "\x0a" or $s eq "\x0d" or $s eq "\x20" ) { + if ( !$self->{incr_nest} ) { + $p--; # do not eat the whitespace, let the next round do it + last INCR_PARSE; + } + next; + } elsif ( $s eq 't' or $s eq 'f' or $s eq 'n' ) { + $self->{incr_mode} = INCR_M_TFN; + redo INCR_PARSE; + } elsif ( $s =~ /^[0-9\-]$/ ) { + $self->{incr_mode} = INCR_M_NUM; + redo INCR_PARSE; + } elsif ( $s eq '"' ) { + $self->{incr_mode} = INCR_M_STR; + redo INCR_PARSE; + } elsif ( $s eq '[' or $s eq '{' ) { + if ( ++$self->{incr_nest} > $coder->get_max_depth ) { + Carp::croak('json text or perl structure exceeds maximum nesting level (max_depth set too low?)'); + } + next; + } elsif ( $s eq ']' or $s eq '}' ) { + if ( --$self->{incr_nest} <= 0 ) { + last INCR_PARSE; + } + } elsif ( $s eq '#' ) { + $self->{incr_mode} = INCR_M_C1; + redo INCR_PARSE; + } + } + } + } + + $self->{incr_pos} = $p; + $self->{incr_parsing} = $p ? 1 : 0; # for backward compatibility +} + + +sub incr_text { + if ( $_[0]->{incr_pos} ) { + Carp::croak("incr_text cannot be called when the incremental parser already started parsing"); + } + $_[0]->{incr_text}; +} + + +sub incr_skip { + my $self = shift; + $self->{incr_text} = substr( $self->{incr_text}, $self->{incr_pos} ); + $self->{incr_pos} = 0; + $self->{incr_mode} = 0; + $self->{incr_nest} = 0; +} + + +sub incr_reset { + my $self = shift; + $self->{incr_text} = undef; + $self->{incr_pos} = 0; + $self->{incr_mode} = 0; + $self->{incr_nest} = 0; +} + +############################### + + +1; +__END__ +=pod + +=head1 NAME + +JSON::PP - JSON::XS compatible pure-Perl module. + +=head1 SYNOPSIS + + use JSON::PP; + + # exported functions, they croak on error + # and expect/generate UTF-8 + + $utf8_encoded_json_text = encode_json $perl_hash_or_arrayref; + $perl_hash_or_arrayref = decode_json $utf8_encoded_json_text; + + # OO-interface + + $json = JSON::PP->new->ascii->pretty->allow_nonref; + + $pretty_printed_json_text = $json->encode( $perl_scalar ); + $perl_scalar = $json->decode( $json_text ); + + # Note that JSON version 2.0 and above will automatically use + # JSON::XS or JSON::PP, so you should be able to just: + + use JSON; + + +=head1 VERSION + + 4.02 + +=head1 DESCRIPTION + +JSON::PP is a pure perl JSON decoder/encoder, and (almost) compatible to much +faster L written by Marc Lehmann in C. JSON::PP works as +a fallback module when you use L module without having +installed JSON::XS. + +Because of this fallback feature of JSON.pm, JSON::PP tries not to +be more JavaScript-friendly than JSON::XS (i.e. not to escape extra +characters such as U+2028 and U+2029, etc), +in order for you not to lose such JavaScript-friendliness silently +when you use JSON.pm and install JSON::XS for speed or by accident. +If you need JavaScript-friendly RFC7159-compliant pure perl module, +try L, which is derived from L web +framework and is also smaller and faster than JSON::PP. + +JSON::PP has been in the Perl core since Perl 5.14, mainly for +CPAN toolchain modules to parse META.json. + +=head1 FUNCTIONAL INTERFACE + +This section is taken from JSON::XS almost verbatim. C +and C are exported by default. + +=head2 encode_json + + $json_text = encode_json $perl_scalar + +Converts the given Perl data structure to a UTF-8 encoded, binary string +(that is, the string contains octets only). Croaks on error. + +This function call is functionally identical to: + + $json_text = JSON::PP->new->utf8->encode($perl_scalar) + +Except being faster. + +=head2 decode_json + + $perl_scalar = decode_json $json_text + +The opposite of C: expects an UTF-8 (binary) string and tries +to parse that as an UTF-8 encoded JSON text, returning the resulting +reference. Croaks on error. + +This function call is functionally identical to: + + $perl_scalar = JSON::PP->new->utf8->decode($json_text) + +Except being faster. + +=head2 JSON::PP::is_bool + + $is_boolean = JSON::PP::is_bool($scalar) + +Returns true if the passed scalar represents either JSON::PP::true or +JSON::PP::false, two constants that act like C<1> and C<0> respectively +and are also used to represent JSON C and C in Perl strings. + +See L, below, for more information on how JSON values are mapped to +Perl. + +=head1 OBJECT-ORIENTED INTERFACE + +This section is also taken from JSON::XS. + +The object oriented interface lets you configure your own encoding or +decoding style, within the limits of supported formats. + +=head2 new + + $json = JSON::PP->new + +Creates a new JSON::PP object that can be used to de/encode JSON +strings. All boolean flags described below are by default I +(with the exception of C, which defaults to I since +version C<4.0>). + +The mutators for flags all return the JSON::PP object again and thus calls can +be chained: + + my $json = JSON::PP->new->utf8->space_after->encode({a => [1,2]}) + => {"a": [1, 2]} + +=head2 ascii + + $json = $json->ascii([$enable]) + + $enabled = $json->get_ascii + +If C<$enable> is true (or missing), then the C method will not +generate characters outside the code range C<0..127> (which is ASCII). Any +Unicode characters outside that range will be escaped using either a +single \uXXXX (BMP characters) or a double \uHHHH\uLLLLL escape sequence, +as per RFC4627. The resulting encoded JSON text can be treated as a native +Unicode string, an ascii-encoded, latin1-encoded or UTF-8 encoded string, +or any other superset of ASCII. + +If C<$enable> is false, then the C method will not escape Unicode +characters unless required by the JSON syntax or other flags. This results +in a faster and more compact format. + +See also the section I later in this document. + +The main use for this flag is to produce JSON texts that can be +transmitted over a 7-bit channel, as the encoded JSON texts will not +contain any 8 bit characters. + + JSON::PP->new->ascii(1)->encode([chr 0x10401]) + => ["\ud801\udc01"] + +=head2 latin1 + + $json = $json->latin1([$enable]) + + $enabled = $json->get_latin1 + +If C<$enable> is true (or missing), then the C method will encode +the resulting JSON text as latin1 (or iso-8859-1), escaping any characters +outside the code range C<0..255>. The resulting string can be treated as a +latin1-encoded JSON text or a native Unicode string. The C method +will not be affected in any way by this flag, as C by default +expects Unicode, which is a strict superset of latin1. + +If C<$enable> is false, then the C method will not escape Unicode +characters unless required by the JSON syntax or other flags. + +See also the section I later in this document. + +The main use for this flag is efficiently encoding binary data as JSON +text, as most octets will not be escaped, resulting in a smaller encoded +size. The disadvantage is that the resulting JSON text is encoded +in latin1 (and must correctly be treated as such when storing and +transferring), a rare encoding for JSON. It is therefore most useful when +you want to store data structures known to contain binary data efficiently +in files or databases, not when talking to other JSON encoders/decoders. + + JSON::PP->new->latin1->encode (["\x{89}\x{abc}"] + => ["\x{89}\\u0abc"] # (perl syntax, U+abc escaped, U+89 not) + +=head2 utf8 + + $json = $json->utf8([$enable]) + + $enabled = $json->get_utf8 + +If C<$enable> is true (or missing), then the C method will encode +the JSON result into UTF-8, as required by many protocols, while the +C method expects to be handled an UTF-8-encoded string. Please +note that UTF-8-encoded strings do not contain any characters outside the +range C<0..255>, they are thus useful for bytewise/binary I/O. In future +versions, enabling this option might enable autodetection of the UTF-16 +and UTF-32 encoding families, as described in RFC4627. + +If C<$enable> is false, then the C method will return the JSON +string as a (non-encoded) Unicode string, while C expects thus a +Unicode string. Any decoding or encoding (e.g. to UTF-8 or UTF-16) needs +to be done yourself, e.g. using the Encode module. + +See also the section I later in this document. + +Example, output UTF-16BE-encoded JSON: + + use Encode; + $jsontext = encode "UTF-16BE", JSON::PP->new->encode ($object); + +Example, decode UTF-32LE-encoded JSON: + + use Encode; + $object = JSON::PP->new->decode (decode "UTF-32LE", $jsontext); + +=head2 pretty + + $json = $json->pretty([$enable]) + +This enables (or disables) all of the C, C and +C (and in the future possibly more) flags in one call to +generate the most readable (or most compact) form possible. + +=head2 indent + + $json = $json->indent([$enable]) + + $enabled = $json->get_indent + +If C<$enable> is true (or missing), then the C method will use a multiline +format as output, putting every array member or object/hash key-value pair +into its own line, indenting them properly. + +If C<$enable> is false, no newlines or indenting will be produced, and the +resulting JSON text is guaranteed not to contain any C. + +This setting has no effect when decoding JSON texts. + +The default indent space length is three. +You can use C to change the length. + +=head2 space_before + + $json = $json->space_before([$enable]) + + $enabled = $json->get_space_before + +If C<$enable> is true (or missing), then the C method will add an extra +optional space before the C<:> separating keys from values in JSON objects. + +If C<$enable> is false, then the C method will not add any extra +space at those places. + +This setting has no effect when decoding JSON texts. You will also +most likely combine this setting with C. + +Example, space_before enabled, space_after and indent disabled: + + {"key" :"value"} + +=head2 space_after + + $json = $json->space_after([$enable]) + + $enabled = $json->get_space_after + +If C<$enable> is true (or missing), then the C method will add an extra +optional space after the C<:> separating keys from values in JSON objects +and extra whitespace after the C<,> separating key-value pairs and array +members. + +If C<$enable> is false, then the C method will not add any extra +space at those places. + +This setting has no effect when decoding JSON texts. + +Example, space_before and indent disabled, space_after enabled: + + {"key": "value"} + +=head2 relaxed + + $json = $json->relaxed([$enable]) + + $enabled = $json->get_relaxed + +If C<$enable> is true (or missing), then C will accept some +extensions to normal JSON syntax (see below). C will not be +affected in anyway. I. I suggest only to use this option to +parse application-specific files written by humans (configuration files, +resource files etc.) + +If C<$enable> is false (the default), then C will only accept +valid JSON texts. + +Currently accepted extensions are: + +=over 4 + +=item * list items can have an end-comma + +JSON I array elements and key-value pairs with commas. This +can be annoying if you write JSON texts manually and want to be able to +quickly append elements, so this extension accepts comma at the end of +such items not just between them: + + [ + 1, + 2, <- this comma not normally allowed + ] + { + "k1": "v1", + "k2": "v2", <- this comma not normally allowed + } + +=item * shell-style '#'-comments + +Whenever JSON allows whitespace, shell-style comments are additionally +allowed. They are terminated by the first carriage-return or line-feed +character, after which more white-space and comments are allowed. + + [ + 1, # this comment not allowed in JSON + # neither this one... + ] + +=item * C-style multiple-line '/* */'-comments (JSON::PP only) + +Whenever JSON allows whitespace, C-style multiple-line comments are additionally +allowed. Everything between C and C<*/> is a comment, after which +more white-space and comments are allowed. + + [ + 1, /* this comment not allowed in JSON */ + /* neither this one... */ + ] + +=item * C++-style one-line '//'-comments (JSON::PP only) + +Whenever JSON allows whitespace, C++-style one-line comments are additionally +allowed. They are terminated by the first carriage-return or line-feed +character, after which more white-space and comments are allowed. + + [ + 1, // this comment not allowed in JSON + // neither this one... + ] + +=item * literal ASCII TAB characters in strings + +Literal ASCII TAB characters are now allowed in strings (and treated as +C<\t>). + + [ + "Hello\tWorld", + "HelloWorld", # literal would not normally be allowed + ] + +=back + +=head2 canonical + + $json = $json->canonical([$enable]) + + $enabled = $json->get_canonical + +If C<$enable> is true (or missing), then the C method will output JSON objects +by sorting their keys. This is adding a comparatively high overhead. + +If C<$enable> is false, then the C method will output key-value +pairs in the order Perl stores them (which will likely change between runs +of the same script, and can change even within the same run from 5.18 +onwards). + +This option is useful if you want the same data structure to be encoded as +the same JSON text (given the same overall settings). If it is disabled, +the same hash might be encoded differently even if contains the same data, +as key-value pairs have no inherent ordering in Perl. + +This setting has no effect when decoding JSON texts. + +This setting has currently no effect on tied hashes. + +=head2 allow_nonref + + $json = $json->allow_nonref([$enable]) + + $enabled = $json->get_allow_nonref + +Unlike other boolean options, this opotion is enabled by default beginning +with version C<4.0>. + +If C<$enable> is true (or missing), then the C method can convert a +non-reference into its corresponding string, number or null JSON value, +which is an extension to RFC4627. Likewise, C will accept those JSON +values instead of croaking. + +If C<$enable> is false, then the C method will croak if it isn't +passed an arrayref or hashref, as JSON texts must either be an object +or array. Likewise, C will croak if given something that is not a +JSON object or array. + +Example, encode a Perl scalar as JSON value without enabled C, +resulting in an error: + + JSON::PP->new->allow_nonref(0)->encode ("Hello, World!") + => hash- or arrayref expected... + +=head2 allow_unknown + + $json = $json->allow_unknown([$enable]) + + $enabled = $json->get_allow_unknown + +If C<$enable> is true (or missing), then C will I throw an +exception when it encounters values it cannot represent in JSON (for +example, filehandles) but instead will encode a JSON C value. Note +that blessed objects are not included here and are handled separately by +c. + +If C<$enable> is false (the default), then C will throw an +exception when it encounters anything it cannot encode as JSON. + +This option does not affect C in any way, and it is recommended to +leave it off unless you know your communications partner. + +=head2 allow_blessed + + $json = $json->allow_blessed([$enable]) + + $enabled = $json->get_allow_blessed + +See L for details. + +If C<$enable> is true (or missing), then the C method will not +barf when it encounters a blessed reference that it cannot convert +otherwise. Instead, a JSON C value is encoded instead of the object. + +If C<$enable> is false (the default), then C will throw an +exception when it encounters a blessed object that it cannot convert +otherwise. + +This setting has no effect on C. + +=head2 convert_blessed + + $json = $json->convert_blessed([$enable]) + + $enabled = $json->get_convert_blessed + +See L for details. + +If C<$enable> is true (or missing), then C, upon encountering a +blessed object, will check for the availability of the C method +on the object's class. If found, it will be called in scalar context and +the resulting scalar will be encoded instead of the object. + +The C method may safely call die if it wants. If C +returns other blessed objects, those will be handled in the same +way. C must take care of not causing an endless recursion cycle +(== crash) in this case. The name of C was chosen because other +methods called by the Perl core (== not by the user of the object) are +usually in upper case letters and to avoid collisions with any C +function or method. + +If C<$enable> is false (the default), then C will not consider +this type of conversion. + +This setting has no effect on C. + +=head2 allow_tags + + $json = $json->allow_tags([$enable]) + + $enabled = $json->get_allow_tags + +See L for details. + +If C<$enable> is true (or missing), then C, upon encountering a +blessed object, will check for the availability of the C method on +the object's class. If found, it will be used to serialise the object into +a nonstandard tagged JSON value (that JSON decoders cannot decode). + +It also causes C to parse such tagged JSON values and deserialise +them via a call to the C method. + +If C<$enable> is false (the default), then C will not consider +this type of conversion, and tagged JSON values will cause a parse error +in C, as if tags were not part of the grammar. + +=head2 boolean_values + + $json->boolean_values([$false, $true]) + + ($false, $true) = $json->get_boolean_values + +By default, JSON booleans will be decoded as overloaded +C<$JSON::PP::false> and C<$JSON::PP::true> objects. + +With this method you can specify your own boolean values for decoding - +on decode, JSON C will be decoded as a copy of C<$false>, and JSON +C will be decoded as C<$true> ("copy" here is the same thing as +assigning a value to another variable, i.e. C<$copy = $false>). + +This is useful when you want to pass a decoded data structure directly +to other serialisers like YAML, Data::MessagePack and so on. + +Note that this works only when you C. You can set incompatible +boolean objects (like L), but when you C a data structure +with such boolean objects, you still need to enable C +(and add a C method if necessary). + +Calling this method without any arguments will reset the booleans +to their default values. + +C will return both C<$false> and C<$true> values, or +the empty list when they are set to the default. + +=head2 filter_json_object + + $json = $json->filter_json_object([$coderef]) + +When C<$coderef> is specified, it will be called from C each +time it decodes a JSON object. The only argument is a reference to +the newly-created hash. If the code references returns a single scalar +(which need not be a reference), this value (or rather a copy of it) is +inserted into the deserialised data structure. If it returns an empty +list (NOTE: I C, which is a valid scalar), the original +deserialised hash will be inserted. This setting can slow down decoding +considerably. + +When C<$coderef> is omitted or undefined, any existing callback will +be removed and C will not change the deserialised hash in any +way. + +Example, convert all JSON objects into the integer 5: + + my $js = JSON::PP->new->filter_json_object(sub { 5 }); + # returns [5] + $js->decode('[{}]'); + # returns 5 + $js->decode('{"a":1, "b":2}'); + +=head2 filter_json_single_key_object + + $json = $json->filter_json_single_key_object($key [=> $coderef]) + +Works remotely similar to C, but is only called for +JSON objects having a single key named C<$key>. + +This C<$coderef> is called before the one specified via +C, if any. It gets passed the single value in the JSON +object. If it returns a single value, it will be inserted into the data +structure. If it returns nothing (not even C but the empty list), +the callback from C will be called next, as if no +single-key callback were specified. + +If C<$coderef> is omitted or undefined, the corresponding callback will be +disabled. There can only ever be one callback for a given key. + +As this callback gets called less often then the C +one, decoding speed will not usually suffer as much. Therefore, single-key +objects make excellent targets to serialise Perl objects into, especially +as single-key JSON objects are as close to the type-tagged value concept +as JSON gets (it's basically an ID/VALUE tuple). Of course, JSON does not +support this in any way, so you need to make sure your data never looks +like a serialised Perl hash. + +Typical names for the single object key are C<__class_whatever__>, or +C<$__dollars_are_rarely_used__$> or C<}ugly_brace_placement>, or even +things like C<__class_md5sum(classname)__>, to reduce the risk of clashing +with real hashes. + +Example, decode JSON objects of the form C<< { "__widget__" => } >> +into the corresponding C<< $WIDGET{} >> object: + + # return whatever is in $WIDGET{5}: + JSON::PP + ->new + ->filter_json_single_key_object (__widget__ => sub { + $WIDGET{ $_[0] } + }) + ->decode ('{"__widget__": 5') + + # this can be used with a TO_JSON method in some "widget" class + # for serialisation to json: + sub WidgetBase::TO_JSON { + my ($self) = @_; + + unless ($self->{id}) { + $self->{id} = ..get..some..id..; + $WIDGET{$self->{id}} = $self; + } + + { __widget__ => $self->{id} } + } + +=head2 shrink + + $json = $json->shrink([$enable]) + + $enabled = $json->get_shrink + +If C<$enable> is true (or missing), the string returned by C will +be shrunk (i.e. downgraded if possible). + +The actual definition of what shrink does might change in future versions, +but it will always try to save space at the expense of time. + +If C<$enable> is false, then JSON::PP does nothing. + +=head2 max_depth + + $json = $json->max_depth([$maximum_nesting_depth]) + + $max_depth = $json->get_max_depth + +Sets the maximum nesting level (default C<512>) accepted while encoding +or decoding. If a higher nesting level is detected in JSON text or a Perl +data structure, then the encoder and decoder will stop and croak at that +point. + +Nesting level is defined by number of hash- or arrayrefs that the encoder +needs to traverse to reach a given point or the number of C<{> or C<[> +characters without their matching closing parenthesis crossed to reach a +given character in a string. + +Setting the maximum depth to one disallows any nesting, so that ensures +that the object is only a single hash/object or array. + +If no argument is given, the highest possible setting will be used, which +is rarely useful. + +See L for more info on why this is useful. + +=head2 max_size + + $json = $json->max_size([$maximum_string_size]) + + $max_size = $json->get_max_size + +Set the maximum length a JSON text may have (in bytes) where decoding is +being attempted. The default is C<0>, meaning no limit. When C +is called on a string that is longer then this many bytes, it will not +attempt to decode the string but throw an exception. This setting has no +effect on C (yet). + +If no argument is given, the limit check will be deactivated (same as when +C<0> is specified). + +See L for more info on why this is useful. + +=head2 encode + + $json_text = $json->encode($perl_scalar) + +Converts the given Perl value or data structure to its JSON +representation. Croaks on error. + +=head2 decode + + $perl_scalar = $json->decode($json_text) + +The opposite of C: expects a JSON text and tries to parse it, +returning the resulting simple scalar or reference. Croaks on error. + +=head2 decode_prefix + + ($perl_scalar, $characters) = $json->decode_prefix($json_text) + +This works like the C method, but instead of raising an exception +when there is trailing garbage after the first JSON object, it will +silently stop parsing there and return the number of characters consumed +so far. + +This is useful if your JSON texts are not delimited by an outer protocol +and you need to know where the JSON text ends. + + JSON::PP->new->decode_prefix ("[1] the tail") + => ([1], 3) + +=head1 FLAGS FOR JSON::PP ONLY + +The following flags and properties are for JSON::PP only. If you use +any of these, you can't make your application run faster by replacing +JSON::PP with JSON::XS. If you need these and also speed boost, +you might want to try L, a fork of JSON::XS by +Reini Urban, which supports some of these (with a different set of +incompatibilities). Most of these historical flags are only kept +for backward compatibility, and should not be used in a new application. + +=head2 allow_singlequote + + $json = $json->allow_singlequote([$enable]) + $enabled = $json->get_allow_singlequote + +If C<$enable> is true (or missing), then C will accept +invalid JSON texts that contain strings that begin and end with +single quotation marks. C will not be affected in any way. +I. I suggest only to use this option to +parse application-specific files written by humans (configuration +files, resource files etc.) + +If C<$enable> is false (the default), then C will only accept +valid JSON texts. + + $json->allow_singlequote->decode(qq|{"foo":'bar'}|); + $json->allow_singlequote->decode(qq|{'foo':"bar"}|); + $json->allow_singlequote->decode(qq|{'foo':'bar'}|); + +=head2 allow_barekey + + $json = $json->allow_barekey([$enable]) + $enabled = $json->get_allow_barekey + +If C<$enable> is true (or missing), then C will accept +invalid JSON texts that contain JSON objects whose names don't +begin and end with quotation marks. C will not be affected +in any way. I. I suggest only to use this option to +parse application-specific files written by humans (configuration +files, resource files etc.) + +If C<$enable> is false (the default), then C will only accept +valid JSON texts. + + $json->allow_barekey->decode(qq|{foo:"bar"}|); + +=head2 allow_bignum + + $json = $json->allow_bignum([$enable]) + $enabled = $json->get_allow_bignum + +If C<$enable> is true (or missing), then C will convert +big integers Perl cannot handle as integer into L +objects and convert floating numbers into L +objects. C will convert C and C +objects into JSON numbers. + + $json->allow_nonref->allow_bignum; + $bigfloat = $json->decode('2.000000000000000000000000001'); + print $json->encode($bigfloat); + # => 2.000000000000000000000000001 + +See also L. + +=head2 loose + + $json = $json->loose([$enable]) + $enabled = $json->get_loose + +If C<$enable> is true (or missing), then C will accept +invalid JSON texts that contain unescaped [\x00-\x1f\x22\x5c] +characters. C will not be affected in any way. +I. I suggest only to use this option to +parse application-specific files written by humans (configuration +files, resource files etc.) + +If C<$enable> is false (the default), then C will only accept +valid JSON texts. + + $json->loose->decode(qq|["abc + def"]|); + +=head2 escape_slash + + $json = $json->escape_slash([$enable]) + $enabled = $json->get_escape_slash + +If C<$enable> is true (or missing), then C will explicitly +escape I (solidus; C) characters to reduce the risk of +XSS (cross site scripting) that may be caused by C<< >> +in a JSON text, with the cost of bloating the size of JSON texts. + +This option may be useful when you embed JSON in HTML, but embedding +arbitrary JSON in HTML (by some HTML template toolkit or by string +interpolation) is risky in general. You must escape necessary +characters in correct order, depending on the context. + +C will not be affected in any way. + +=head2 indent_length + + $json = $json->indent_length($number_of_spaces) + $length = $json->get_indent_length + +This option is only useful when you also enable C or C. + +JSON::XS indents with three spaces when you C (if requested +by C or C), and the number cannot be changed. +JSON::PP allows you to change/get the number of indent spaces with these +mutator/accessor. The default number of spaces is three (the same as +JSON::XS), and the acceptable range is from C<0> (no indentation; +it'd be better to disable indentation by C) to C<15>. + +=head2 sort_by + + $json = $json->sort_by($code_ref) + $json = $json->sort_by($subroutine_name) + +If you just want to sort keys (names) in JSON objects when you +C, enable C option (see above) that allows you to +sort object keys alphabetically. + +If you do need to sort non-alphabetically for whatever reasons, +you can give a code reference (or a subroutine name) to C, +then the argument will be passed to Perl's C built-in function. + +As the sorting is done in the JSON::PP scope, you usually need to +prepend C to the subroutine name, and the special variables +C<$a> and C<$b> used in the subrontine used by C function. + +Example: + + my %ORDER = (id => 1, class => 2, name => 3); + $json->sort_by(sub { + ($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) + or $JSON::PP::a cmp $JSON::PP::b + }); + print $json->encode([ + {name => 'CPAN', id => 1, href => 'http://cpan.org'} + ]); + # [{"id":1,"name":"CPAN","href":"http://cpan.org"}] + +Note that C affects all the plain hashes in the data structure. +If you need finer control, C necessary hashes with a module that +implements ordered hash (such as L and L). +C and C don't affect the key order in Cd +hashes. + + use Hash::Ordered; + tie my %hash, 'Hash::Ordered', + (name => 'CPAN', id => 1, href => 'http://cpan.org'); + print $json->encode([\%hash]); + # [{"name":"CPAN","id":1,"href":"http://cpan.org"}] # order is kept + +=head1 INCREMENTAL PARSING + +This section is also taken from JSON::XS. + +In some cases, there is the need for incremental parsing of JSON +texts. While this module always has to keep both JSON text and resulting +Perl data structure in memory at one time, it does allow you to parse a +JSON stream incrementally. It does so by accumulating text until it has +a full JSON object, which it then can decode. This process is similar to +using C to see if a full JSON object is available, but +is much more efficient (and can be implemented with a minimum of method +calls). + +JSON::PP will only attempt to parse the JSON text once it is sure it +has enough text to get a decisive result, using a very simple but +truly incremental parser. This means that it sometimes won't stop as +early as the full parser, for example, it doesn't detect mismatched +parentheses. The only thing it guarantees is that it starts decoding as +soon as a syntactically valid JSON text has been seen. This means you need +to set resource limits (e.g. C) to ensure the parser will stop +parsing in the presence if syntax errors. + +The following methods implement this incremental parser. + +=head2 incr_parse + + $json->incr_parse( [$string] ) # void context + + $obj_or_undef = $json->incr_parse( [$string] ) # scalar context + + @obj_or_empty = $json->incr_parse( [$string] ) # list context + +This is the central parsing function. It can both append new text and +extract objects from the stream accumulated so far (both of these +functions are optional). + +If C<$string> is given, then this string is appended to the already +existing JSON fragment stored in the C<$json> object. + +After that, if the function is called in void context, it will simply +return without doing anything further. This can be used to add more text +in as many chunks as you want. + +If the method is called in scalar context, then it will try to extract +exactly I JSON object. If that is successful, it will return this +object, otherwise it will return C. If there is a parse error, +this method will croak just as C would do (one can then use +C to skip the erroneous part). This is the most common way of +using the method. + +And finally, in list context, it will try to extract as many objects +from the stream as it can find and return them, or the empty list +otherwise. For this to work, there must be no separators (other than +whitespace) between the JSON objects or arrays, instead they must be +concatenated back-to-back. If an error occurs, an exception will be +raised as in the scalar context case. Note that in this case, any +previously-parsed JSON texts will be lost. + +Example: Parse some JSON arrays/objects in a given string and return +them. + + my @objs = JSON::PP->new->incr_parse ("[5][7][1,2]"); + +=head2 incr_text + + $lvalue_string = $json->incr_text + +This method returns the currently stored JSON fragment as an lvalue, that +is, you can manipulate it. This I works when a preceding call to +C in I successfully returned an object. Under +all other circumstances you must not call this function (I mean it. +although in simple tests it might actually work, it I fail under +real world conditions). As a special exception, you can also call this +method before having parsed anything. + +That means you can only use this function to look at or manipulate text +before or after complete JSON objects, not while the parser is in the +middle of parsing a JSON object. + +This function is useful in two cases: a) finding the trailing text after a +JSON object or b) parsing multiple JSON objects separated by non-JSON text +(such as commas). + +=head2 incr_skip + + $json->incr_skip + +This will reset the state of the incremental parser and will remove +the parsed text from the input buffer so far. This is useful after +C died, in which case the input buffer and incremental parser +state is left unchanged, to skip the text parsed so far and to reset the +parse state. + +The difference to C is that only text until the parse error +occurred is removed. + +=head2 incr_reset + + $json->incr_reset + +This completely resets the incremental parser, that is, after this call, +it will be as if the parser had never parsed anything. + +This is useful if you want to repeatedly parse JSON objects and want to +ignore any trailing data, which means you have to reset the parser after +each successful decode. + +=head1 MAPPING + +Most of this section is also taken from JSON::XS. + +This section describes how JSON::PP maps Perl values to JSON values and +vice versa. These mappings are designed to "do the right thing" in most +circumstances automatically, preserving round-tripping characteristics +(what you put in comes out as something equivalent). + +For the more enlightened: note that in the following descriptions, +lowercase I refers to the Perl interpreter, while uppercase I +refers to the abstract Perl language itself. + +=head2 JSON -> PERL + +=over 4 + +=item object + +A JSON object becomes a reference to a hash in Perl. No ordering of object +keys is preserved (JSON does not preserve object key ordering itself). + +=item array + +A JSON array becomes a reference to an array in Perl. + +=item string + +A JSON string becomes a string scalar in Perl - Unicode codepoints in JSON +are represented by the same codepoints in the Perl string, so no manual +decoding is necessary. + +=item number + +A JSON number becomes either an integer, numeric (floating point) or +string scalar in perl, depending on its range and any fractional parts. On +the Perl level, there is no difference between those as Perl handles all +the conversion details, but an integer may take slightly less memory and +might represent more values exactly than floating point numbers. + +If the number consists of digits only, JSON::PP will try to represent +it as an integer value. If that fails, it will try to represent it as +a numeric (floating point) value if that is possible without loss of +precision. Otherwise it will preserve the number as a string value (in +which case you lose roundtripping ability, as the JSON number will be +re-encoded to a JSON string). + +Numbers containing a fractional or exponential part will always be +represented as numeric (floating point) values, possibly at a loss of +precision (in which case you might lose perfect roundtripping ability, but +the JSON number will still be re-encoded as a JSON number). + +Note that precision is not accuracy - binary floating point values cannot +represent most decimal fractions exactly, and when converting from and to +floating point, JSON::PP only guarantees precision up to but not including +the least significant bit. + +When C is enabled, big integer values and any numeric +values will be converted into L and L +objects respectively, without becoming string scalars or losing +precision. + +=item true, false + +These JSON atoms become C and C, +respectively. They are overloaded to act almost exactly like the numbers +C<1> and C<0>. You can check whether a scalar is a JSON boolean by using +the C function. + +=item null + +A JSON null atom becomes C in Perl. + +=item shell-style comments (C<< # I >>) + +As a nonstandard extension to the JSON syntax that is enabled by the +C setting, shell-style comments are allowed. They can start +anywhere outside strings and go till the end of the line. + +=item tagged values (C<< (I)I >>). + +Another nonstandard extension to the JSON syntax, enabled with the +C setting, are tagged values. In this implementation, the +I must be a perl package/class name encoded as a JSON string, and the +I must be a JSON array encoding optional constructor arguments. + +See L, below, for details. + +=back + + +=head2 PERL -> JSON + +The mapping from Perl to JSON is slightly more difficult, as Perl is a +truly typeless language, so we can only guess which JSON type is meant by +a Perl value. + +=over 4 + +=item hash references + +Perl hash references become JSON objects. As there is no inherent +ordering in hash keys (or JSON objects), they will usually be encoded +in a pseudo-random order. JSON::PP can optionally sort the hash keys +(determined by the I flag and/or I property), so +the same data structure will serialise to the same JSON text (given +same settings and version of JSON::PP), but this incurs a runtime +overhead and is only rarely useful, e.g. when you want to compare some +JSON text against another for equality. + +=item array references + +Perl array references become JSON arrays. + +=item other references + +Other unblessed references are generally not allowed and will cause an +exception to be thrown, except for references to the integers C<0> and +C<1>, which get turned into C and C atoms in JSON. You can +also use C and C to improve +readability. + + to_json [\0, JSON::PP::true] # yields [false,true] + +=item JSON::PP::true, JSON::PP::false + +These special values become JSON true and JSON false values, +respectively. You can also use C<\1> and C<\0> directly if you want. + +=item JSON::PP::null + +This special value becomes JSON null. + +=item blessed objects + +Blessed objects are not directly representable in JSON, but C +allows various ways of handling objects. See L, +below, for details. + +=item simple scalars + +Simple Perl scalars (any scalar that is not a reference) are the most +difficult objects to encode: JSON::PP will encode undefined scalars as +JSON C values, scalars that have last been used in a string context +before encoding as JSON strings, and anything else as number value: + + # dump as number + encode_json [2] # yields [2] + encode_json [-3.0e17] # yields [-3e+17] + my $value = 5; encode_json [$value] # yields [5] + + # used as string, so dump as string + print $value; + encode_json [$value] # yields ["5"] + + # undef becomes null + encode_json [undef] # yields [null] + +You can force the type to be a JSON string by stringifying it: + + my $x = 3.1; # some variable containing a number + "$x"; # stringified + $x .= ""; # another, more awkward way to stringify + print $x; # perl does it for you, too, quite often + # (but for older perls) + +You can force the type to be a JSON number by numifying it: + + my $x = "3"; # some variable containing a string + $x += 0; # numify it, ensuring it will be dumped as a number + $x *= 1; # same thing, the choice is yours. + +You can not currently force the type in other, less obscure, ways. + +Since version 2.91_01, JSON::PP uses a different number detection logic +that converts a scalar that is possible to turn into a number safely. +The new logic is slightly faster, and tends to help people who use older +perl or who want to encode complicated data structure. However, this may +results in a different JSON text from the one JSON::XS encodes (and +thus may break tests that compare entire JSON texts). If you do +need the previous behavior for compatibility or for finer control, +set PERL_JSON_PP_USE_B environmental variable to true before you +C JSON::PP (or JSON.pm). + +Note that numerical precision has the same meaning as under Perl (so +binary to decimal conversion follows the same rules as in Perl, which +can differ to other languages). Also, your perl interpreter might expose +extensions to the floating point numbers of your platform, such as +infinities or NaN's - these cannot be represented in JSON, and it is an +error to pass those in. + +JSON::PP (and JSON::XS) trusts what you pass to C method +(or C function) is a clean, validated data structure with +values that can be represented as valid JSON values only, because it's +not from an external data source (as opposed to JSON texts you pass to +C or C, which JSON::PP considers tainted and +doesn't trust). As JSON::PP doesn't know exactly what you and consumers +of your JSON texts want the unexpected values to be (you may want to +convert them into null, or to stringify them with or without +normalisation (string representation of infinities/NaN may vary +depending on platforms), or to croak without conversion), you're advised +to do what you and your consumers need before you encode, and also not +to numify values that may start with values that look like a number +(including infinities/NaN), without validating. + +=back + +=head2 OBJECT SERIALISATION + +As JSON cannot directly represent Perl objects, you have to choose between +a pure JSON representation (without the ability to deserialise the object +automatically again), and a nonstandard extension to the JSON syntax, +tagged values. + +=head3 SERIALISATION + +What happens when C encounters a Perl object depends on the +C, C, C and C +settings, which are used in this order: + +=over 4 + +=item 1. C is enabled and the object has a C method. + +In this case, C creates a tagged JSON value, using a nonstandard +extension to the JSON syntax. + +This works by invoking the C method on the object, with the first +argument being the object to serialise, and the second argument being the +constant string C to distinguish it from other serialisers. + +The C method can return any number of values (i.e. zero or +more). These values and the paclkage/classname of the object will then be +encoded as a tagged JSON value in the following format: + + ("classname")[FREEZE return values...] + +e.g.: + + ("URI")["http://www.google.com/"] + ("MyDate")[2013,10,29] + ("ImageData::JPEG")["Z3...VlCg=="] + +For example, the hypothetical C C method might use the +objects C and C members to encode the object: + + sub My::Object::FREEZE { + my ($self, $serialiser) = @_; + + ($self->{type}, $self->{id}) + } + +=item 2. C is enabled and the object has a C method. + +In this case, the C method of the object is invoked in scalar +context. It must return a single scalar that can be directly encoded into +JSON. This scalar replaces the object in the JSON text. + +For example, the following C method will convert all L +objects to JSON strings when serialised. The fact that these values +originally were L objects is lost. + + sub URI::TO_JSON { + my ($uri) = @_; + $uri->as_string + } + +=item 3. C is enabled and the object is a C or C. + +The object will be serialised as a JSON number value. + +=item 4. C is enabled. + +The object will be serialised as a JSON null value. + +=item 5. none of the above + +If none of the settings are enabled or the respective methods are missing, +C throws an exception. + +=back + +=head3 DESERIALISATION + +For deserialisation there are only two cases to consider: either +nonstandard tagging was used, in which case C decides, +or objects cannot be automatically be deserialised, in which +case you can use postprocessing or the C or +C callbacks to get some real objects our of +your JSON. + +This section only considers the tagged value case: a tagged JSON object +is encountered during decoding and C is disabled, a parse +error will result (as if tagged values were not part of the grammar). + +If C is enabled, C will look up the C method +of the package/classname used during serialisation (it will not attempt +to load the package as a Perl module). If there is no such method, the +decoding will fail with an error. + +Otherwise, the C method is invoked with the classname as first +argument, the constant string C as second argument, and all the +values from the JSON array (the values originally returned by the +C method) as remaining arguments. + +The method must then return the object. While technically you can return +any Perl scalar, you might have to enable the C setting to +make that work in all cases, so better return an actual blessed reference. + +As an example, let's implement a C function that regenerates the +C from the C example earlier: + + sub My::Object::THAW { + my ($class, $serialiser, $type, $id) = @_; + + $class->new (type => $type, id => $id) + } + + +=head1 ENCODING/CODESET FLAG NOTES + +This section is taken from JSON::XS. + +The interested reader might have seen a number of flags that signify +encodings or codesets - C, C and C. There seems to be +some confusion on what these do, so here is a short comparison: + +C controls whether the JSON text created by C (and expected +by C) is UTF-8 encoded or not, while C and C only +control whether C escapes character values outside their respective +codeset range. Neither of these flags conflict with each other, although +some combinations make less sense than others. + +Care has been taken to make all flags symmetrical with respect to +C and C, that is, texts encoded with any combination of +these flag values will be correctly decoded when the same flags are used +- in general, if you use different flag settings while encoding vs. when +decoding you likely have a bug somewhere. + +Below comes a verbose discussion of these flags. Note that a "codeset" is +simply an abstract set of character-codepoint pairs, while an encoding +takes those codepoint numbers and I them, in our case into +octets. Unicode is (among other things) a codeset, UTF-8 is an encoding, +and ISO-8859-1 (= latin 1) and ASCII are both codesets I encodings at +the same time, which can be confusing. + +=over 4 + +=item C flag disabled + +When C is disabled (the default), then C/C generate +and expect Unicode strings, that is, characters with high ordinal Unicode +values (> 255) will be encoded as such characters, and likewise such +characters are decoded as-is, no changes to them will be done, except +"(re-)interpreting" them as Unicode codepoints or Unicode characters, +respectively (to Perl, these are the same thing in strings unless you do +funny/weird/dumb stuff). + +This is useful when you want to do the encoding yourself (e.g. when you +want to have UTF-16 encoded JSON texts) or when some other layer does +the encoding for you (for example, when printing to a terminal using a +filehandle that transparently encodes to UTF-8 you certainly do NOT want +to UTF-8 encode your data first and have Perl encode it another time). + +=item C flag enabled + +If the C-flag is enabled, C/C will encode all +characters using the corresponding UTF-8 multi-byte sequence, and will +expect your input strings to be encoded as UTF-8, that is, no "character" +of the input string must have any value > 255, as UTF-8 does not allow +that. + +The C flag therefore switches between two modes: disabled means you +will get a Unicode string in Perl, enabled means you get an UTF-8 encoded +octet/binary string in Perl. + +=item C or C flags enabled + +With C (or C) enabled, C will escape characters +with ordinal values > 255 (> 127 with C) and encode the remaining +characters as specified by the C flag. + +If C is disabled, then the result is also correctly encoded in those +character sets (as both are proper subsets of Unicode, meaning that a +Unicode string with all character values < 256 is the same thing as a +ISO-8859-1 string, and a Unicode string with all character values < 128 is +the same thing as an ASCII string in Perl). + +If C is enabled, you still get a correct UTF-8-encoded string, +regardless of these flags, just some more characters will be escaped using +C<\uXXXX> then before. + +Note that ISO-8859-1-I strings are not compatible with UTF-8 +encoding, while ASCII-encoded strings are. That is because the ISO-8859-1 +encoding is NOT a subset of UTF-8 (despite the ISO-8859-1 I being +a subset of Unicode), while ASCII is. + +Surprisingly, C will ignore these flags and so treat all input +values as governed by the C flag. If it is disabled, this allows you +to decode ISO-8859-1- and ASCII-encoded strings, as both strict subsets of +Unicode. If it is enabled, you can correctly decode UTF-8 encoded strings. + +So neither C nor C are incompatible with the C flag - +they only govern when the JSON output engine escapes a character or not. + +The main use for C is to relatively efficiently store binary data +as JSON, at the expense of breaking compatibility with most JSON decoders. + +The main use for C is to force the output to not contain characters +with values > 127, which means you can interpret the resulting string +as UTF-8, ISO-8859-1, ASCII, KOI8-R or most about any character set and +8-bit-encoding, and still get the same data structure back. This is useful +when your channel for JSON transfer is not 8-bit clean or the encoding +might be mangled in between (e.g. in mail), and works because ASCII is a +proper subset of most 8-bit and multibyte encodings in use in the world. + +=back + +=head1 BUGS + +Please report bugs on a specific behavior of this module to RT or GitHub +issues (preferred): + +L + +L + +As for new features and requests to change common behaviors, please +ask the author of JSON::XS (Marc Lehmann, Eschmorp[at]schmorp.deE) +first, by email (important!), to keep compatibility among JSON.pm backends. + +Generally speaking, if you need something special for you, you are advised +to create a new module, maybe based on L, which is smaller and +written in a much cleaner way than this module. + +=head1 SEE ALSO + +The F command line utility for quick experiments. + +L, L, and L for faster alternatives. +L and L for easy migration. + +L and L for older perl users. + +RFC4627 (L) + +RFC7159 (L) + +RFC8259 (L) + +=head1 AUTHOR + +Makamaka Hannyaharamitu, Emakamaka[at]cpan.orgE + +=head1 CURRENT MAINTAINER + +Kenichi Ishigaki, Eishigaki[at]cpan.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2007-2016 by Makamaka Hannyaharamitu + +Most of the documentation is taken from JSON::XS by Marc Lehmann + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/scripts/update-cmd.pl b/scripts/update-cmd.pl new file mode 100755 index 0000000..37d0b11 --- /dev/null +++ b/scripts/update-cmd.pl @@ -0,0 +1,208 @@ +#!/usr/bin/perl +use FindBin qw($Bin); +require "$Bin/json_pp.pm"; +use Data::Dumper; +use strict; + +sub create_state() { + return { + route => {}, + ipaddr => {}, + }; +} + +sub cmd($) { + my $cmd = shift; + print STDERR "command: $cmd\n"; + system($cmd); +} + +sub fetch_active_data_linux($$) { + my $ifname = shift; + my $data = shift; + + open DATA, "(ip -4 r s dev $ifname; ip -6 r s dev $ifname) |"; + while () { + chomp; + s/^\s+//; + my @data = split /\s+/, $_; + next if $data[0] =~ /^fe80:/; + next if $data[0] =~ /^ff..:/; + $data[0] =~ /(:|\/)/ or do { + $data[0] .= '/32'; + }; + $data->{route}->{$data[0]} = 'delete'; + } + close DATA; + + open DATA, "ip a s dev $ifname |"; + while () { + chomp; + s/^\s+//; + my @data = split /\s+/, $_; + next unless $data[0] =~ /inet/; + next if $data[1] =~ /^fe80:/; + $data->{ipaddr}->{$data[1]} = 'delete'; + } + close DATA; +} + +sub fetch_active_data_darwin($$) { + my $ifname = shift; + my $data = shift; + + open DATA, "netstat -rn |"; + while () { + chomp; + s/^\s+//; + my @data = split /\s+/, $_; + next unless $data[3] eq $ifname; + next if $data[0] =~ /^fe80:/; + next if $data[0] =~ /^ff..:/; + $data[0] =~ /(:|\/)/ or do { + my $mask = 32; + my @addr = split /\./, $data[0]; + while (@addr < 4) { + push @addr, '0'; + $mask -= 8; + } + $data[0] = join(".", @addr)."/$mask"; + }; + $data->{route}->{$data[0]} = 'delete'; + } + close DATA; + + open DATA, "ifconfig $ifname |"; + while () { + chomp; + s/^\s+//; + my @data = split /\s+/, $_; + next unless $data[0] =~ /inet/; + next if $data[1] =~ /^fe80:/; + $data->{ipaddr}->{$data[1]} = 'delete'; + } + close DATA; +} + +sub update_data($$$) { + my $data = shift; + my $delete = shift; + my $add = shift; + + return unless $data->{"link-up"} eq 1; + foreach my $val (@{$data->{ipaddr}}, @{$data->{ip6addr}}) { + my $ip = $val->{ipaddr}; + my $mask = $val->{mask}; + + if ($ip =~ /:/) { + my $route = $ip; + if (not($ip =~ /::/) and $mask eq 64) { + $route =~ s/((\w+):(\w+):(\w+):(\w+)):.*/$1::/; + } else { + $route = "$ip/128"; + } + push @{$data->{routes6}}, { target => "$route", "netmask" => $mask }; + } else { + push @{$data->{routes}}, { target => "$ip", "netmask" => 32 }; + }; + if ($delete->{ipaddr}->{$ip}) { + delete $delete->{ipaddr}->{$ip}; + } elsif ($delete->{ipaddr}->{"$ip/$mask"}) { + delete $delete->{ipaddr}->{"$ip/$mask"}; + } else { + $add->{ipaddr}->{"$ip/$mask"} = 'add'; + } + } + foreach my $val (@{$data->{routes}}, @{$data->{routes6}}) { + my $ip = $val->{target}.'/'.$val->{netmask}; + + if ($delete->{route}->{$ip}) { + delete $delete->{route}->{$ip}; + } else { + $add->{route}->{$ip} = 'add'; + } + } +} + +sub set_active_data_linux($$$) { + my $ifname = shift; + my $delete = shift; + my $add = shift; + + foreach my $ip (keys %{$delete->{ipaddr}}) { + cmd("ip a d $ip dev $ifname"); + } + foreach my $ip (keys %{$add->{ipaddr}}) { + cmd("ip a a $ip dev $ifname"); + } + + foreach my $route (keys %{$delete->{route}}) { + cmd("ip r d $route dev $ifname"); + } + foreach my $route (keys %{$add->{route}}) { + cmd("ip r a $route dev $ifname"); + } +} + +sub set_active_data_darwin($$$) { + my $ifname = shift; + my $delete = shift; + my $add = shift; + + foreach my $ip (keys %{$delete->{ipaddr}}) { + $ip =~ s/\/.*//; + if ($ip =~ /:/) { + cmd("ifconfig $ifname inet6 delete $ip"); + } else { + cmd("ifconfig $ifname delete $ip"); + } + } + foreach my $ip (keys %{$add->{ipaddr}}) { + my @ip = split /\//, $ip; + + if ($ip[0] =~ /:/) { + cmd("ifconfig $ifname inet6 add $ip[0] prefixlen $ip[1]"); + } else { + cmd("ifconfig $ifname add $ip[0]/$ip[1] $ip[0]"); + } + } + foreach my $route (keys %{$delete->{route}}) { + if ($route =~ /:/) { + cmd("route delete -inet6 $route -iface $ifname"); + } else { + cmd("route delete -inet $route -iface $ifname"); + } + } + foreach my $route (keys %{$add->{route}}) { + if ($route =~ /:/) { + cmd("route add -inet6 $route -iface $ifname"); + } else { + cmd("route add -inet $route -iface $ifname"); + } + } +} + +my $json = $ARGV[0]; +my $platform = `uname`; +my $data = JSON::PP::decode_json($json) or die "Failed to decode JSON data\n"; + +my $delete = create_state(); +my $add = create_state(); + +if ($platform =~ /Darwin/) { + fetch_active_data_darwin($data->{ifname}, $delete); +} elsif ($platform =~ /Linux/) { + fetch_active_data_linux($data->{ifname}, $delete); +} else { + die "Unsupported platform $platform\n"; +} + +update_data($data, $delete, $add); + +if ($platform =~ /Darwin/) { + set_active_data_darwin($data->{ifname}, $delete, $add); +} elsif ($platform =~ /Linux/) { + set_active_data_linux($data->{ifname}, $delete, $add); +} + +# print Data::Dumper->Dump([$add, $delete], ["add", "delete"])."\n"; diff --git a/service.c b/service.c new file mode 100644 index 0000000..908fe3b --- /dev/null +++ b/service.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include "unetd.h" + +enum { + SERVICE_ATTR_TYPE, + SERVICE_ATTR_MEMBERS, + __SERVICE_ATTR_MAX +}; + +static const struct blobmsg_policy service_policy[__SERVICE_ATTR_MAX] = { + [SERVICE_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING }, + [SERVICE_ATTR_MEMBERS] = { "members", BLOBMSG_TYPE_ARRAY }, +}; + +void network_services_init(struct network *net) +{ + avl_init(&net->services, avl_strcmp, false, NULL); +} + +void network_services_free(struct network *net) +{ + struct network_service *s, *tmp; + + avl_remove_all_elements(&net->services, s, node, tmp) + free(s); +} + +static int +__service_parse_members(struct network *net, struct network_service *s, + const char *name) +{ + struct network_group *group; + struct network_host *host; + + if (name[0] != '@') { + host = avl_find_element(&net->hosts, name, host, node); + + if (!host) + return 0; + + if (s) + s->members[s->n_members++] = host; + + return 1; + } + + name++; + group = avl_find_element(&net->groups, name, group, node); + if (!group) + return 0; + + if (s) { + memcpy(&s->members[s->n_members], group->members, + group->n_members * sizeof(group->members[0])); + s->n_members += group->n_members; + } + + return group->n_members; +} + +static int +service_parse_members(struct network *net, struct network_service *s, + struct blob_attr *data) +{ + struct blob_attr *cur; + int rem; + int n = 0; + + blobmsg_for_each_attr(cur, data, rem) + n += __service_parse_members(net, s, blobmsg_get_string(cur)); + + return n; +} + +static void +service_add(struct network *net, struct blob_attr *data) +{ + struct network_service *s; + struct blob_attr *tb[__SERVICE_ATTR_MAX]; + struct blob_attr *cur; + const char *name = blobmsg_name(data); + const char *type = NULL; + char *name_buf, *type_buf; + int n_members; + + blobmsg_parse(service_policy, __SERVICE_ATTR_MAX, tb, + blobmsg_data(data), blobmsg_len(data)); + + if ((cur = tb[SERVICE_ATTR_TYPE]) != NULL) + type = blobmsg_get_string(cur); + + if (blobmsg_check_array(tb[SERVICE_ATTR_MEMBERS], BLOBMSG_TYPE_STRING) < 0) + return; + + n_members = service_parse_members(net, NULL, tb[SERVICE_ATTR_MEMBERS]); + s = calloc_a(sizeof(*s) + n_members * sizeof(s->members[0]), + &name_buf, strlen(name) + 1, + &type_buf, type ? strlen(type) + 1 : 0); + + s->node.key = strcpy(name_buf, name); + if (type) + s->type = strcpy(type_buf, type); + + service_parse_members(net, s, tb[SERVICE_ATTR_MEMBERS]); + avl_insert(&net->services, &s->node); +} + +void network_services_add(struct network *net, struct blob_attr *data) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, data, rem) + service_add(net, cur); +} diff --git a/service.h b/service.h new file mode 100644 index 0000000..ff652b7 --- /dev/null +++ b/service.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_SERVICE_H +#define __UNETD_SERVICE_H + +struct network_service { + struct avl_node node; + + const char *type; + + int n_members; + struct network_host *members[]; +}; + +void network_services_init(struct network *net); +void network_services_free(struct network *net); +void network_services_add(struct network *net, struct blob_attr *data); + +#endif diff --git a/siphash.c b/siphash.c new file mode 100644 index 0000000..5020543 --- /dev/null +++ b/siphash.c @@ -0,0 +1,75 @@ +/* Copyright (C) 2016 Jason A. Donenfeld . All Rights Reserved. + * + * This file is provided under a dual BSD/GPLv2 license. + * + * SipHash: a fast short-input PRF + * https://131002.net/siphash/ + * + * This implementation is specifically for SipHash2-4 for a secure PRF + */ + +#include +#include "siphash.h" + +static inline uint64_t rol64(uint64_t word, unsigned int shift) +{ + return (word << (shift & 63)) | (word >> ((-shift) & 63)); +} + + +#define SIPROUND \ + do { \ + v0 += v1; v1 = rol64(v1, 13); v1 ^= v0; v0 = rol64(v0, 32); \ + v2 += v3; v3 = rol64(v3, 16); v3 ^= v2; \ + v0 += v3; v3 = rol64(v3, 21); v3 ^= v0; \ + v2 += v1; v1 = rol64(v1, 17); v1 ^= v2; v2 = rol64(v2, 32); \ + } while (0) + +#define PREAMBLE(len) \ + uint64_t v0 = 0x736f6d6570736575ULL; \ + uint64_t v1 = 0x646f72616e646f6dULL; \ + uint64_t v2 = 0x6c7967656e657261ULL; \ + uint64_t v3 = 0x7465646279746573ULL; \ + uint64_t b = ((uint64_t)(len)) << 56; \ + v3 ^= key->key[1]; \ + v2 ^= key->key[0]; \ + v1 ^= key->key[1]; \ + v0 ^= key->key[0]; + +#define POSTAMBLE \ + v3 ^= b; \ + SIPROUND; \ + SIPROUND; \ + v0 ^= b; \ + v2 ^= 0xff; \ + SIPROUND; \ + SIPROUND; \ + SIPROUND; \ + SIPROUND; \ + return (v0 ^ v1) ^ (v2 ^ v3); + + +uint64_t siphash(const void *data, size_t len, const siphash_key_t *key) +{ + const uint8_t *end = data + len - (len % sizeof(uint64_t)); + const uint8_t left = len & (sizeof(uint64_t) - 1); + uint64_t m; + PREAMBLE(len) + for (; data != end; data += sizeof(uint64_t)) { + m = get_unaligned_le64(data); + v3 ^= m; + SIPROUND; + SIPROUND; + v0 ^= m; + } + switch (left) { + case 7: b |= ((uint64_t)end[6]) << 48; fallthrough; + case 6: b |= ((uint64_t)end[5]) << 40; fallthrough; + case 5: b |= ((uint64_t)end[4]) << 32; fallthrough; + case 4: b |= get_unaligned_le32(end); break; + case 3: b |= ((uint64_t)end[2]) << 16; fallthrough; + case 2: b |= get_unaligned_le16(end); break; + case 1: b |= end[0]; + } + POSTAMBLE +} diff --git a/siphash.h b/siphash.h new file mode 100644 index 0000000..ff76d9c --- /dev/null +++ b/siphash.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2016 Jason A. Donenfeld . All Rights Reserved. + * + * This file is provided under a dual BSD/GPLv2 license. + * + * SipHash: a fast short-input PRF + * https://131002.net/siphash/ + * + * This implementation is specifically for SipHash2-4 for a secure PRF + * and HalfSipHash1-3/SipHash1-3 for an insecure PRF only suitable for + * hashtables. + */ + +#ifndef _LINUX_SIPHASH_H +#define _LINUX_SIPHASH_H + +#include +#include +#include +#include + +#define SIPHASH_ALIGNMENT __alignof__(uint64_t) +typedef struct { + uint64_t key[2]; +} siphash_key_t; + +static inline uint16_t get_unaligned_le16(const uint8_t *p) +{ + return p[0] | p[1] << 8; +} + +static inline uint32_t get_unaligned_le32(const uint8_t *p) +{ + return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24; +} + +static inline uint64_t get_unaligned_le64(const uint8_t *p) +{ + return (uint64_t)get_unaligned_le32(p + 4) << 32 | + get_unaligned_le32(p); +} + +static inline bool siphash_key_is_zero(const siphash_key_t *key) +{ + return !(key->key[0] | key->key[1]); +} + +uint64_t siphash(const void *data, size_t len, const siphash_key_t *key); + +static inline void siphash_to_le64(void *dest, const void *data, size_t len, + const siphash_key_t *key) +{ + uint64_t hash = siphash(data, len, key); + + *(uint64_t *)dest = cpu_to_le64(hash); +} + +#endif /* _LINUX_SIPHASH_H */ diff --git a/ubus.c b/ubus.c new file mode 100644 index 0000000..76c1980 --- /dev/null +++ b/ubus.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include "unetd.h" + +static struct ubus_auto_conn conn; +static struct blob_buf b; + +static int +ubus_network_add(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *name; + + blobmsg_parse(&network_policy[NETWORK_ATTR_NAME], 1, &name, + blobmsg_data(msg), blobmsg_len(msg)); + + if (!name) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (unetd_network_add(blobmsg_get_string(name), msg)) + return UBUS_STATUS_INVALID_ARGUMENT; + + return 0; +} + + +static int +ubus_network_del(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *name; + + blobmsg_parse(&network_policy[NETWORK_ATTR_NAME], 1, &name, + blobmsg_data(msg), blobmsg_len(msg)); + + if (!name) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (unetd_network_remove(blobmsg_get_string(name))) + return UBUS_STATUS_NOT_FOUND; + + return 0; +} + +enum { + SERVICE_ATTR_NETWORK, + SERVICE_ATTR_NAME, + __SERVICE_ATTR_MAX +}; + +static const struct blobmsg_policy service_policy[__SERVICE_ATTR_MAX] = { + [SERVICE_ATTR_NETWORK] = { "network", BLOBMSG_TYPE_STRING }, + [SERVICE_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING }, +}; + + +static void +ubus_service_get_network_members(struct blob_buf *b, struct network *n, + const char *name) +{ + struct network_service *s; + int i; + + s = avl_find_element(&n->services, name, s, node); + if (!s) + return; + + for (i = 0; i < s->n_members; i++) { + struct network_host *host = s->members[i]; + char *name; + + name = blobmsg_alloc_string_buffer(b, NULL, INET6_ADDRSTRLEN); + inet_ntop(AF_INET6, &host->peer.local_addr.in6, name, INET6_ADDRSTRLEN); + blobmsg_add_string_buffer(b); + } +} + + +static int +ubus_service_get(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SERVICE_ATTR_MAX]; + struct blob_attr *cur; + struct network *n = NULL; + const char *name; + void *c; + + blobmsg_parse(service_policy, __SERVICE_ATTR_MAX, tb, + blobmsg_data(msg), blobmsg_len(msg)); + + if ((cur = tb[SERVICE_ATTR_NAME]) != NULL) + name = blobmsg_get_string(cur); + else + return UBUS_STATUS_INVALID_ARGUMENT; + + if ((cur = tb[SERVICE_ATTR_NETWORK]) != NULL) { + n = avl_find_element(&networks, blobmsg_get_string(cur), n, node); + if (!n) + return UBUS_STATUS_INVALID_ARGUMENT; + } + + blob_buf_init(&b, 0); + + c = blobmsg_open_array(&b, "hosts"); + if (n) { + ubus_service_get_network_members(&b, n, name); + } else { + avl_for_each_element(&networks, n, node) + ubus_service_get_network_members(&b, n, name); + } + blobmsg_close_array(&b, c); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +static const struct ubus_method unetd_methods[] = { + UBUS_METHOD("network_add", ubus_network_add, network_policy), + UBUS_METHOD_MASK("network_del", ubus_network_del, network_policy, + (1 << NETWORK_ATTR_NAME)), + UBUS_METHOD("service_get", ubus_service_get, service_policy), +}; + +static struct ubus_object_type unetd_object_type = + UBUS_OBJECT_TYPE("unetd", unetd_methods); + +static struct ubus_object unetd_object = { + .name = "unetd", + .type = &unetd_object_type, + .methods = unetd_methods, + .n_methods = ARRAY_SIZE(unetd_methods), +}; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + int ret; + + ret = ubus_add_object(ctx, &unetd_object); + if (ret) + fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); +} + +void unetd_ubus_netifd_update(struct blob_attr *data) +{ + uint32_t id; + + if (ubus_lookup_id(&conn.ctx, "network.interface", &id)) + return; + + ubus_invoke(&conn.ctx, id, "notify_proto", data, NULL, NULL, 5000); +} + +void unetd_ubus_netifd_add_route(struct network *net, union network_endpoint *ep) +{ + uint32_t id; + void *addr; + char *buf; + + if (!net->config.interface) + return; + + if (ubus_lookup_id(&conn.ctx, "network", &id)) + return; + + blob_buf_init(&b, 0); + + if (ep->in.sin_family == AF_INET6) + addr = &ep->in6.sin6_addr; + else + addr = &ep->in.sin_addr; + + blobmsg_add_u8(&b, "v6", ep->in.sin_family == AF_INET6); + buf = blobmsg_alloc_string_buffer(&b, "target", INET6_ADDRSTRLEN); + inet_ntop(ep->in.sin_family, addr, buf, INET6_ADDRSTRLEN); + blobmsg_add_string_buffer(&b); + blobmsg_add_string(&b, "interface", net->config.interface); + blobmsg_add_u8(&b, "exclude", true); + + ubus_invoke(&conn.ctx, id, "add_host_route", b.head, NULL, NULL, -1); +} + +void unetd_ubus_init(void) +{ + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); +} diff --git a/unetd.h b/unetd.h new file mode 100644 index 0000000..412aeb4 --- /dev/null +++ b/unetd.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_H +#define __UNETD_H + +#include + +#include +#include +#include +#include "utils.h" +#include "siphash.h" +#include "wg.h" +#include "pex.h" +#include "network.h" +#include "host.h" +#include "service.h" + +extern bool dummy_mode; +extern bool debug; + +#define D(format, ...) \ + do { \ + if (debug) \ + fprintf(stderr, "%s(%d) " format "\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + +#define D_NET(net, format, ...) D("network %s " format, network_name(net), ##__VA_ARGS__) +#define D_HOST(net, host, format, ...) D_NET(net, "host %s " format, network_host_name(host), ##__VA_ARGS__) +#define D_PEER(net, peer, format, ...) D_NET(net, "host %s " format, network_peer_name(peer), ##__VA_ARGS__) + + +void unetd_write_hosts(void); +void unetd_ubus_init(void); +void unetd_ubus_netifd_update(struct blob_attr *data); +void unetd_ubus_netifd_add_route(struct network *net, union network_endpoint *ep); + +#endif diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..462ebbf --- /dev/null +++ b/utils.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include "unetd.h" + +int network_get_endpoint(union network_endpoint *dest, const char *str, + int default_port, int idx) +{ + struct addrinfo hints = { + .ai_flags = AI_ADDRCONFIG, + .ai_family = AF_UNSPEC, + }; + char *buf = strdup(str); + char *host = buf, *port; + struct addrinfo *ai, *ai_cur; + int n_res; + int ret = -1; + + memset(dest, 0, sizeof(*dest)); + + if (*host == '[') { + host++; + port = strchr(host, ']'); + if (!port) + return -1; + + *(port++) = 0; + if (!*port) + port = NULL; + else if (*port == ':') + port++; + else + goto out; + hints.ai_family = AF_INET6; + hints.ai_flags |= AI_NUMERICHOST; + } else { + host = buf; + + port = strchr(host, ':'); + if (port) + *(port++) = 0; + } + + if (getaddrinfo(host, port, &hints, &ai) || !ai) + goto out; + + while (1) { + ai_cur = ai; + for (n_res = 0; ai_cur; ai_cur = ai_cur->ai_next, n_res++) + if (!idx--) + goto found; + + idx %= n_res; + } + +found: + if (ai_cur->ai_addrlen > sizeof(*dest)) + goto out; + + memcpy(dest, ai_cur->ai_addr, ai_cur->ai_addrlen); + if (!port) + dest->in.sin_port = htons(default_port); + ret = 0; + +out: + free(buf); + return ret; +} + +int network_get_subnet(int af, union network_addr *addr, int *mask, const char *str) +{ + char *buf = strdup(str); + char *sep, *end; + int ret = -1; + + if (af == AF_INET6) + *mask = 128; + else + *mask = 32; + + sep = strchr(buf, '/'); + if (sep) { + unsigned long val; + + *(sep++) = 0; + + val = strtoul(sep, &end, 0); + if ((end && *end) || val > *mask) + goto out; + + *mask = val; + } + + if (inet_pton(af, buf, addr) == 1) + ret = 0; + +out: + free(buf); + return ret; +} + +int network_get_local_addr(void *local, const union network_endpoint *target) +{ + union network_endpoint ep = {}; + socklen_t len; + int ret = -1; + int fd; + + memset(local, 0, sizeof(union network_addr)); + if (target->sa.sa_family == AF_INET6) + len = sizeof(ep.in6); + else + len = sizeof(ep.in); + + fd = socket(target->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + if (connect(fd, (const struct sockaddr *)target, len)) + goto out; + + len = sizeof(ep); + if (getsockname(fd, &ep.sa, &len)) + goto out; + + if (ep.sa.sa_family == AF_INET6) + memcpy(local, &ep.in6.sin6_addr, sizeof(ep.in6.sin6_addr)); + else + memcpy(local, &ep.in.sin_addr, sizeof(ep.in.sin_addr)); + ret = 0; + +out: + close(fd); + return ret; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..eac083e --- /dev/null +++ b/utils.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_UTILS_H +#define __UNETD_UTILS_H + +#include + +union network_addr { + struct { + uint8_t network_id[8]; + uint8_t host_addr[8]; + }; + struct in_addr in; + struct in6_addr in6; +}; + +union network_endpoint { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +}; + +static inline void * +network_endpoint_addr(union network_endpoint *ep, int *addr_len) +{ + if (ep->sa.sa_family == AF_INET6) { + *addr_len = sizeof(ep->in6.sin6_addr); + return &ep->in6.sin6_addr; + } + + *addr_len = sizeof(ep->in.sin_addr); + return &ep->in.sin_addr; +} + +static inline bool +network_endpoint_addr_equal(union network_endpoint *ep1, union network_endpoint *ep2) +{ + const void *a1, *a2; + int len; + + if (ep1->sa.sa_family != ep2->sa.sa_family) + return false; + + a1 = network_endpoint_addr(ep1, &len); + a2 = network_endpoint_addr(ep2, &len); + + return !memcmp(a1, a2, len); +} + +int network_get_endpoint(union network_endpoint *dest, const char *str, + int default_port, int idx); +int network_get_subnet(int af, union network_addr *addr, int *mask, + const char *str); +int network_get_local_addr(void *local, const union network_endpoint *target); + +#endif diff --git a/wg-dummy.c b/wg-dummy.c new file mode 100644 index 0000000..d378272 --- /dev/null +++ b/wg-dummy.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include "unetd.h" + +static int +wg_dummy_init(struct network *net) +{ + char key[B64_ENCODE_LEN(CURVE25519_KEY_SIZE)]; + + fprintf(stderr, "Create wireguard interface %s\n", network_name(net)); + b64_encode(net->config.key, sizeof(net->config.key), key, sizeof(key)); + fprintf(stderr, "key=%s\n", key); + b64_encode(net->config.pubkey, sizeof(net->config.pubkey), key, sizeof(key)); + fprintf(stderr, "pubkey=%s\n", key); + fprintf(stderr, "\n"); + + return 0; +} + +static void +wg_dummy_cleanup(struct network *net) +{ + fprintf(stderr, "Remove wireguard interface %s\n", network_name(net)); +} + +static int +wg_dummy_init_local(struct network *net, struct network_peer *peer) +{ + char addr[INET6_ADDRSTRLEN]; + + fprintf(stderr, "local node %s, port: %d\n", network_peer_name(peer), peer->port); + + fprintf(stderr, "default addr: %s\n", + inet_ntop(AF_INET6, &peer->local_addr.in6, addr, sizeof(addr))); + + fprintf(stderr, "\n"); + + return 0; +} + +static int +wg_dummy_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd) +{ + static const char * const cmds[] = { + [WG_PEER_CREATE] = "create", + [WG_PEER_UPDATE] = "update", + [WG_PEER_DELETE] = "delete", + }; + char key[B64_ENCODE_LEN(CURVE25519_KEY_SIZE)]; + char addr[INET6_ADDRSTRLEN]; + struct blob_attr *cur; + int rem; + + b64_encode(peer->key, sizeof(peer->key), key, sizeof(key)); + fprintf(stderr, "%s peer %s: %s\n", cmds[cmd], network_peer_name(peer), key); + + if (cmd == WG_PEER_DELETE) + return 0; + + fprintf(stderr, "default addr: %s\n", + inet_ntop(AF_INET6, &peer->local_addr.in6, addr, sizeof(addr))); + + blobmsg_for_each_attr(cur, peer->ipaddr, rem) { + fprintf(stderr, "peer addr: %s\n", blobmsg_get_string(cur)); + } + blobmsg_for_each_attr(cur, peer->subnet, rem) { + fprintf(stderr, "peer subnet: %s\n", blobmsg_get_string(cur)); + } + fprintf(stderr, "\n"); + return 0; +} + +static int +wg_dummy_peer_refresh(struct network *net) +{ + struct network_host *host; + + avl_for_each_element(&net->hosts, host, node) { + struct network_peer *peer = &host->peer; + + if (peer->state.endpoint.in.sin_family) + peer->state.connected = true; + } + + return 0; +} + +static int +wg_dummy_peer_connect(struct network *net, struct network_peer *peer, + union network_endpoint *ep) +{ + char addr[INET6_ADDRSTRLEN]; + void *ip; + + if (ep->in.sin_family == AF_INET6) + ip = &ep->in6.sin6_addr; + else + ip = &ep->in.sin_addr; + + fprintf(stderr, "connect to host %s at %s:%d\n", network_peer_name(peer), + inet_ntop(ep->in.sin_family, ip, addr, sizeof(addr)), ntohs(ep->in.sin_port)); + memcpy(&peer->state.endpoint, ep, sizeof(peer->state.endpoint)); + + return 0; +} + +const struct wg_ops wg_dummy_ops = { + .name = "dummy", + .init = wg_dummy_init, + .cleanup = wg_dummy_cleanup, + .init_local = wg_dummy_init_local, + .peer_update = wg_dummy_peer_update, + .peer_refresh = wg_dummy_peer_refresh, + .peer_connect = wg_dummy_peer_connect, +}; diff --git a/wg-linux.c b/wg-linux.c new file mode 100644 index 0000000..2f578c6 --- /dev/null +++ b/wg-linux.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + * + * Based on wireguard-tools: + * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unetd.h" + +struct timespec64 { + int64_t tv_sec; + int64_t tv_nsec; +}; + +struct wg_linux_peer_req { + struct nl_msg *msg; + + struct nlattr *peers, *entry; +}; + +static struct unl unl; + +static int +wg_nl_init(void) +{ + if (unl.sock) + return 0; + + return unl_genl_init(&unl, "wireguard"); +} + +static struct nl_msg * +wg_genl_msg(struct network *net, bool set) +{ + struct nl_msg *msg; + + msg = unl_genl_msg(&unl, set ? WG_CMD_SET_DEVICE : WG_CMD_GET_DEVICE, !set); + nla_put_string(msg, WGDEVICE_A_IFNAME, network_name(net)); + + return msg; +} + +static int +wg_genl_call(struct nl_msg *msg) +{ + return unl_request(&unl, msg, NULL, NULL); +} + +static int +__wg_linux_init(struct network *net, void *key) +{ + struct nl_msg *msg; + + msg = wg_genl_msg(net, true); + nla_put(msg, WGDEVICE_A_PRIVATE_KEY, WG_KEY_LEN, key); + nla_put_u32(msg, WGDEVICE_A_FLAGS, WGDEVICE_F_REPLACE_PEERS); + + return wg_genl_call(msg); +} + +static void +wg_linux_cleanup(struct network *net) +{ + uint8_t key[WG_KEY_LEN] = {}; + + __wg_linux_init(net, key); +} + +static int +wg_linux_init(struct network *net) +{ + if (wg_nl_init()) + return -1; + + return __wg_linux_init(net, net->config.key); +} + +static int +wg_linux_init_local(struct network *net, struct network_peer *peer) +{ + struct nl_msg *msg; + + msg = wg_genl_msg(net, true); + nla_put_u16(msg, WGDEVICE_A_LISTEN_PORT, peer->port); + + return wg_genl_call(msg); +} + +static void +wg_linux_msg_add_ip(struct nl_msg *msg, int af, void *addr, int mask) +{ + struct nlattr *ip; + int len; + + if (af == AF_INET6) + len = sizeof(struct in6_addr); + else + len = sizeof(struct in_addr); + + ip = nla_nest_start(msg, 0); + nla_put_u16(msg, WGALLOWEDIP_A_FAMILY, af); + nla_put(msg, WGALLOWEDIP_A_IPADDR, len, addr); + nla_put_u8(msg, WGALLOWEDIP_A_CIDR_MASK, mask); + nla_nest_end(msg, ip); +} + +static struct nl_msg * +wg_linux_peer_req_init(struct network *net, struct network_peer *peer, + struct wg_linux_peer_req *req) +{ + req->msg = wg_genl_msg(net, true); + + req->peers = nla_nest_start(req->msg, WGDEVICE_A_PEERS); + req->entry = nla_nest_start(req->msg, 0); + nla_put(req->msg, WGPEER_A_PUBLIC_KEY, WG_KEY_LEN, peer->key); + + return req->msg; +} + +static int +wg_linux_peer_req_done(struct wg_linux_peer_req *req) +{ + nla_nest_end(req->msg, req->entry); + nla_nest_end(req->msg, req->peers); + + return wg_genl_call(req->msg); +} + +static int +wg_linux_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd) +{ + struct wg_linux_peer_req req; + struct blob_attr *cur; + struct nl_msg *msg; + struct nlattr *ips; + int rem; + + msg = wg_linux_peer_req_init(net, peer, &req); + + if (cmd == WG_PEER_DELETE) { + nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME); + goto out; + } + + nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS); + + ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS); + wg_linux_msg_add_ip(msg, AF_INET6, &peer->local_addr.in6, 128); + + blobmsg_for_each_attr(cur, peer->ipaddr, rem) { + const char *str = blobmsg_get_string(cur); + struct in6_addr in6; + int af, mask; + + if (strchr(str, ':')) { + af = AF_INET6; + mask = 128; + } else { + af = AF_INET; + mask = 32; + } + + if (inet_pton(af, str, &in6) != 1) + continue; + + wg_linux_msg_add_ip(msg, af, &in6, mask); + } + + blobmsg_for_each_attr(cur, peer->subnet, rem) { + const char *str = blobmsg_get_string(cur); + union network_addr addr; + int mask; + int af; + + af = strchr(str, ':') ? AF_INET6 : AF_INET; + if (network_get_subnet(af, &addr, &mask, str)) + continue; + + wg_linux_msg_add_ip(msg, af, &addr, mask); + } + + nla_nest_end(msg, ips); + +out: + return wg_linux_peer_req_done(&req); +} + +static void +wg_linux_parse_peer(struct network *net, struct nlattr *data, time_t now) +{ + struct network_peer *peer = NULL; + struct nlattr *tb[__WGPEER_A_LAST]; + struct nlattr *cur; + + nla_parse_nested(tb, WGPEER_A_MAX, data, NULL); + + cur = tb[WGPEER_A_PUBLIC_KEY]; + if (!cur) + return; + + peer = wg_peer_update_start(net, nla_data(cur)); + if (!peer) + return; + + if ((cur = tb[WGPEER_A_LAST_HANDSHAKE_TIME]) != NULL) { + struct timespec64 *tv = nla_data(cur); + + wg_peer_set_last_handshake(net, peer, now, tv->tv_sec); + } + + if ((cur = tb[WGPEER_A_RX_BYTES]) != NULL) + wg_peer_set_rx_bytes(net, peer, nla_get_u64(cur)); + + if ((cur = tb[WGPEER_A_ENDPOINT]) != NULL) + wg_peer_set_endpoint(net, peer, nla_data(cur), nla_len(cur)); + + wg_peer_update_done(net, peer); +} + +static void +wg_linux_parse_peer_list(struct network *net, struct nlattr *data, time_t now) +{ + struct nlattr *cur; + int rem; + + if (!data) + return; + + nla_for_each_nested(cur, data, rem) + wg_linux_parse_peer(net, cur, now); +} + +static int +wg_linux_get_cb(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct network *net = arg; + struct nlattr *tb[__WGDEVICE_A_LAST]; + time_t now = time(NULL); + + nlmsg_parse(nh, sizeof(struct genlmsghdr), tb, __WGDEVICE_A_LAST, NULL); + wg_linux_parse_peer_list(net, tb[WGDEVICE_A_PEERS], now); + + return NL_SKIP; +} + +static int +wg_linux_peer_refresh(struct network *net) +{ + struct nl_msg *msg = wg_genl_msg(net, false); + + return unl_request(&unl, msg, wg_linux_get_cb, net); +} + +static int +wg_linux_peer_connect(struct network *net, struct network_peer *peer, + union network_endpoint *ep) +{ + struct wg_linux_peer_req req; + struct nl_msg *msg; + int len; + + msg = wg_linux_peer_req_init(net, peer, &req); + + if (net->net_config.keepalive) { + nla_put_u16(msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, 0); + wg_linux_peer_req_done(&req); + + msg = wg_linux_peer_req_init(net, peer, &req); + nla_put_u16(msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + net->net_config.keepalive); + } + + if (ep->in.sin_family == AF_INET6) + len = sizeof(ep->in6); + else + len = sizeof(ep->in); + nla_put(msg, WGPEER_A_ENDPOINT, len, &ep->in6); + + return wg_linux_peer_req_done(&req); +} + +const struct wg_ops wg_linux_ops = { + .name = "user", + .init = wg_linux_init, + .cleanup = wg_linux_cleanup, + .init_local = wg_linux_init_local, + .peer_update = wg_linux_peer_update, + .peer_refresh = wg_linux_peer_refresh, + .peer_connect = wg_linux_peer_connect, +}; diff --git a/wg-user.c b/wg-user.c new file mode 100644 index 0000000..cfe13d1 --- /dev/null +++ b/wg-user.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + * + * Based on wireguard-tools: + * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unetd.h" + +#define SOCK_PATH RUNSTATEDIR "/wireguard/" +#define SOCK_SUFFIX ".sock" + +struct wg_req { + FILE *f; + + char *buf; + size_t buf_len; + + char *key, *value; + + int ret; +}; + +static void +key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]) +{ + unsigned int i; + + for (i = 0; i < WG_KEY_LEN; ++i) { + hex[i * 2] = 87U + (key[i] >> 4) + ((((key[i] >> 4) - 10U) >> 8) & ~38U); + hex[i * 2 + 1] = 87U + (key[i] & 0xf) + ((((key[i] & 0xf) - 10U) >> 8) & ~38U); + } + + hex[i * 2] = '\0'; +} + +static bool +key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex) +{ + uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val; + volatile uint8_t ret = 0; + + if (strlen(hex) != WG_KEY_LEN_HEX - 1) + return false; + + for (unsigned int i = 0; i < WG_KEY_LEN_HEX - 1; i += 2) { + c = (uint8_t)hex[i]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + c_acc = c_val * 16U; + + c = (uint8_t)hex[i + 1]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + ret |= ((c_num0 | c_alpha0) - 1) >> 8; + c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha); + key[i / 2] = c_acc | c_val; + } + + return 1 & ((ret - 1) >> 8); +} + +static bool wg_user_check(struct network *net) +{ + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + struct stat sbuf; + int fd, ret; + + if (snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, network_name(net)) < 0) + return false; + if (stat(addr.sun_path, &sbuf) < 0) + return false; + if (!S_ISSOCK(sbuf.st_mode)) + return false; + ret = fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ret < 0) + return false; + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0 && errno == ECONNREFUSED) { /* If the process is gone, we try to clean up the socket. */ + close(fd); + unlink(addr.sun_path); + return false; + } + close(fd); + return true; +} + +static FILE *wg_user_file(struct network *net) +{ + struct stat sbuf; + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + int fd = -1, ret; + FILE *f = NULL; + + errno = EINVAL; + ret = snprintf(addr.sun_path, sizeof(addr.sun_path), SOCK_PATH "%s" SOCK_SUFFIX, network_name(net)); + if (ret < 0) + goto out; + ret = stat(addr.sun_path, &sbuf); + if (ret < 0) + goto out; + errno = EBADF; + if (!S_ISSOCK(sbuf.st_mode)) + goto out; + + ret = fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ret < 0) + goto out; + + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + if (errno == ECONNREFUSED) /* If the process is gone, we try to clean up the socket. */ + unlink(addr.sun_path); + goto out; + } + f = fdopen(fd, "r+"); + if (f) + errno = 0; +out: + ret = -errno; + if (ret) { + if (fd >= 0) + close(fd); + errno = -ret; + return NULL; + } + return f; +} + +static void wg_req_set(struct wg_req *req, const char *key, const char *value) +{ + fprintf(req->f, "%s=%s\n", key, value); +} + +static void wg_req_set_int(struct wg_req *req, const char *key, int value) +{ + fprintf(req->f, "%s=%d\n", key, value); +} + +#define wg_req_printf(req, name, format, ...) fprintf((req)->f, "%s=" format "\n", name, ##__VA_ARGS__) + +static int wg_req_init(struct wg_req *req, struct network *net, bool set) +{ + memset(req, 0, sizeof(*req)); + req->ret = -1; + req->f = wg_user_file(net); + if (!req->f) + return -1; + + wg_req_set(req, set ? "set" : "get", "1"); + + return 0; +} + +static bool wg_req_fetch(struct wg_req *req) +{ + int len; + + if (!req->buf) { + fprintf(req->f, "\n"); + fflush(req->f); + } + + if (getline(&req->buf, &req->buf_len, req->f) <= 0) + return false; + + req->key = req->buf; + len = strlen(req->key); + if (len == 1 && req->key[0] == '\n') + return false; + + req->value = strchr(req->key, '='); + if (!req->value || !len || req->key[len - 1] != '\n') + return false; + + *(req->value++) = req->key[--len] = 0; + if (!strcmp(req->key, "errno")) + req->ret = atoi(req->value); + + return true; +} + +static void wg_req_complete(struct wg_req *req) +{ + while (wg_req_fetch(req)); +} + +static int wg_req_done(struct wg_req *req) +{ + if (!req->buf) + wg_req_complete(req); + + if (req->f) + fclose(req->f); + free(req->buf); + + return -req->ret; +} + +static int +wg_user_test(struct network *net) +{ + struct wg_req req; + + if (wg_req_init(&req, net, false)) + return -1; + + return wg_req_done(&req); +} + +static int +wg_network_reset(struct network *net, uint8_t *key) +{ + struct wg_req req; + char key_str[WG_KEY_LEN_HEX]; + + if (wg_req_init(&req, net, true)) + return -1; + + wg_req_set(&req, "replace_peers", "true"); + + key_to_hex(key_str, key); + wg_req_set(&req, "private_key", key_str); + + return wg_req_done(&req); +} + +static int +wg_user_init(struct network *net) +{ + int err; + + err = wg_user_test(net); + if (err) + return err; + + return wg_network_reset(net, net->config.key); +} + +static void +wg_user_cleanup(struct network *net) +{ + uint8_t key[WG_KEY_LEN] = {}; + + wg_network_reset(net, key); +} + +static int +wg_user_init_local(struct network *net, struct network_peer *peer) +{ + struct wg_req req; + + if (wg_req_init(&req, net, true)) + return -1; + + wg_req_set_int(&req, "listen_port", peer->port); + + return wg_req_done(&req); +} + +static int +wg_user_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd) +{ + struct blob_attr *cur; + struct wg_req req; + char addr[INET6_ADDRSTRLEN]; + char key[WG_KEY_LEN_HEX]; + int rem; + + if (wg_req_init(&req, net, true)) + return -1; + + key_to_hex(key, peer->key); + wg_req_set(&req, "public_key", key); + + if (cmd == WG_PEER_DELETE) { + wg_req_set(&req, "remove", "true"); + goto out; + } + + wg_req_set(&req, "replace_allowed_ips", "true"); + + inet_ntop(AF_INET6, &peer->local_addr.in6, addr, sizeof(addr)); + wg_req_printf(&req, "allowed_ip", "%s/128", addr); + + blobmsg_for_each_attr(cur, peer->ipaddr, rem) { + const char *str = blobmsg_get_string(cur); + struct in6_addr in6; + int af, mask; + + if (strchr(str, ':')) { + af = AF_INET6; + mask = 128; + } else { + af = AF_INET; + mask = 32; + } + + if (inet_pton(af, str, &in6) != 1) + continue; + + wg_req_printf(&req, "allowed_ip", "%s/%d", str, mask); + } + + blobmsg_for_each_attr(cur, peer->subnet, rem) { + const char *str = blobmsg_get_string(cur); + char buf[INET6_ADDRSTRLEN]; + union network_addr addr; + int mask; + int af; + + af = strchr(str, ':') ? AF_INET6 : AF_INET; + if (network_get_subnet(af, &addr, &mask, str)) + continue; + + inet_ntop(af, &addr, buf, sizeof(buf)); + wg_req_printf(&req, "allowed_ip", "%s/%d", buf, mask); + } + +out: + return wg_req_done(&req); +} + +static int +wg_user_peer_refresh(struct network *net) +{ + struct network_peer *peer = NULL; + struct wg_req req; + uint8_t key[WG_KEY_LEN]; + time_t now = time(NULL); + + if (wg_req_init(&req, net, false)) + return -1; + + while (wg_req_fetch(&req)) { + if (!strcmp(req.key, "public_key")) { + if (peer) + wg_peer_update_done(net, peer); + if (key_from_hex(key, req.value)) + peer = wg_peer_update_start(net, key); + else + peer = NULL; + continue; + } + + if (!peer) + continue; + + if (!strcmp(req.key, "last_handshake_time_sec")) { + uint64_t sec = strtoull(req.value, NULL, 0); + + wg_peer_set_last_handshake(net, peer, now, sec); + continue; + } + + if (!strcmp(req.key, "rx_bytes")) { + uint64_t bytes = strtoull(req.value, NULL, 0); + + wg_peer_set_rx_bytes(net, peer, bytes); + continue; + } + + if (!strcmp(req.key, "endpoint")) { + struct addrinfo *resolved; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, + }; + char *port; + + if (!strlen(req.value)) + continue; + + if (req.value[0] == '[') { + req.value++; + port = strchr(req.value, ']'); + if (!port) + continue; + + *port++ = 0; + if (*port++ != ':') + continue; + } else { + port = strchr(req.value, ':'); + if (!port) + continue; + + *port++ = 0; + } + + if (!*port) + continue; + + if (getaddrinfo(req.value, port, &hints, &resolved) != 0) + continue; + + if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) || + (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))) + wg_peer_set_endpoint(net, peer, resolved->ai_addr, resolved->ai_addrlen); + + freeaddrinfo(resolved); + continue; + } + } + + if (peer) + wg_peer_update_done(net, peer); + + return wg_req_done(&req); +} + +static int +wg_user_peer_connect(struct network *net, struct network_peer *peer, + union network_endpoint *ep) +{ + struct wg_req req; + char addr[INET6_ADDRSTRLEN]; + char key[WG_KEY_LEN_HEX]; + const void *ip; + int port; + + if (wg_req_init(&req, net, true)) + return -1; + + key_to_hex(key, peer->key); + wg_req_set(&req, "public_key", key); + + if (ep->in.sin_family == AF_INET6) + ip = &ep->in6.sin6_addr; + else + ip = &ep->in.sin_addr; + + inet_ntop(ep->in.sin_family, ip, addr, sizeof(addr)); + port = ntohs(ep->in.sin_port); + + if (ep->in.sin_family == AF_INET6) + wg_req_printf(&req, "endpoint", "[%s]:%d", addr, port); + else + wg_req_printf(&req, "endpoint", "%s:%d", addr, port); + + if (net->net_config.keepalive) { + wg_req_set_int(&req, "persistent_keepalive_interval", 0); + wg_req_set_int(&req, "persistent_keepalive_interval", + net->net_config.keepalive); + } + + return wg_req_done(&req); +} + +const struct wg_ops wg_user_ops = { + .name = "user", + .check = wg_user_check, + .init = wg_user_init, + .cleanup = wg_user_cleanup, + .init_local = wg_user_init_local, + .peer_update = wg_user_peer_update, + .peer_refresh = wg_user_peer_refresh, + .peer_connect = wg_user_peer_connect, +}; diff --git a/wg.c b/wg.c new file mode 100644 index 0000000..1d40677 --- /dev/null +++ b/wg.c @@ -0,0 +1,98 @@ +#include "unetd.h" + +static const struct wg_ops *wg_get_ops(struct network *net) +{ + if (dummy_mode) + return &wg_dummy_ops; + + if (wg_user_ops.check(net)) + return &wg_user_ops; + +#ifdef linux + return &wg_linux_ops; +#else + return NULL; +#endif +} + +int wg_init_network(struct network *net) +{ + net->wg.ops = wg_get_ops(net); + + if (!net->wg.ops) + return -1; + + return net->wg.ops->init(net); +} + +void wg_cleanup_network(struct network *net) +{ + if (net->wg.ops) + net->wg.ops->cleanup(net); +} + +struct network_peer *wg_peer_update_start(struct network *net, const uint8_t *key) +{ + struct network_peer *peer; + + peer = vlist_find(&net->peers, key, peer, node); + if (!peer) + return NULL; + + peer->state.handshake = false; + peer->state.idle++; + if (peer->state.idle >= 2 * net->net_config.keepalive) + peer->state.connected = false; + if (peer->state.idle > net->net_config.keepalive) + network_pex_event(net, peer, PEX_EV_PING); + + return peer; +} + +void wg_peer_update_done(struct network *net, struct network_peer *peer) +{ + if (peer->state.handshake) + network_pex_event(net, peer, PEX_EV_HANDSHAKE); +} + +void wg_peer_set_last_handshake(struct network *net, struct network_peer *peer, + uint64_t now, uint64_t sec) +{ + if (sec == peer->state.last_handshake) + return; + + peer->state.handshake = true; + peer->state.last_handshake = sec; + sec = now - sec; + if (sec <= net->net_config.keepalive) { + peer->state.connected = true; + if (peer->state.idle > sec) + peer->state.idle = sec; + } +} + +void wg_peer_set_rx_bytes(struct network *net, struct network_peer *peer, + uint64_t bytes) +{ + int64_t diff = bytes - peer->state.rx_bytes; + + peer->state.rx_bytes = bytes; + if (diff > 0) { + peer->state.idle = 0; + peer->state.connected = true; + } +} + +void wg_peer_set_endpoint(struct network *net, struct network_peer *peer, + void *data, size_t len) +{ + if (len > sizeof(peer->state.endpoint)) + return; + + if (!memcmp(&peer->state.endpoint, data, len)) + return; + + memset(&peer->state.endpoint, 0, sizeof(peer->state.endpoint)); + memcpy(&peer->state.endpoint, data, len); + network_pex_event(net, peer, PEX_EV_ENDPOINT_CHANGE); +} diff --git a/wg.h b/wg.h new file mode 100644 index 0000000..5a7aac1 --- /dev/null +++ b/wg.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Felix Fietkau + */ +#ifndef __UNETD_WG_H +#define __UNETD_WG_H + +#define WG_KEY_LEN 32 +#define WG_KEY_LEN_HEX (WG_KEY_LEN * 2 + 1) + +enum wg_update_cmd { + WG_PEER_CREATE, + WG_PEER_UPDATE, + WG_PEER_DELETE, +}; + +struct network; +struct network_peer; +union network_endpoint; + +struct wg_ops { + const char *name; + + bool (*check)(struct network *net); + + int (*init)(struct network *net); + void (*cleanup)(struct network *net); + int (*init_local)(struct network *net, struct network_peer *peer); + int (*peer_refresh)(struct network *net); + int (*peer_update)(struct network *net, struct network_peer *peer, + enum wg_update_cmd cmd); + int (*peer_connect)(struct network *net, struct network_peer *peer, + union network_endpoint *ep); +}; + +struct wg { + const struct wg_ops *ops; +}; + +extern const struct wg_ops wg_dummy_ops; +extern const struct wg_ops wg_user_ops; +extern const struct wg_ops wg_linux_ops; + +int wg_init_network(struct network *net); +void wg_cleanup_network(struct network *net); + +#define wg_init_local(net, ...) (net)->wg.ops->init_local(net, ##__VA_ARGS__) +#define wg_peer_update(net, ...) (net)->wg.ops->peer_update(net, ##__VA_ARGS__) +#define wg_peer_connect(net, ...) (net)->wg.ops->peer_connect(net, ##__VA_ARGS__) +#define wg_peer_refresh(net) (net)->wg.ops->peer_refresh(net) + +/* internal */ +struct network_peer *wg_peer_update_start(struct network *net, const uint8_t *key); +void wg_peer_update_done(struct network *net, struct network_peer *peer); +void wg_peer_set_last_handshake(struct network *net, struct network_peer *peer, + uint64_t now, uint64_t sec); +void wg_peer_set_rx_bytes(struct network *net, struct network_peer *peer, + uint64_t bytes); +void wg_peer_set_endpoint(struct network *net, struct network_peer *peer, + void *data, size_t len); + +#endif -- 2.30.2