From: Felix Fietkau Date: Thu, 5 May 2022 08:49:46 +0000 (+0200) Subject: initial commit X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=f75c2e70c0e494566dbbb20d408309412b4110e6;p=project%2Funetd.git initial commit Signed-off-by: Felix Fietkau --- f75c2e70c0e494566dbbb20d408309412b4110e6 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