diff --git a/.gitmodules b/.gitmodules index e4aa0cc..d768a2a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "src/log"] path = src/log url = https://github.com/zhaojh329/log.git +[submodule "src/kcp"] + path = src/kcp + url = https://github.com/skywind3000/kcp.git diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5cad24b..ce1f6b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,25 +3,43 @@ set(RTTY_VERSION_MAJOR 9) set(RTTY_VERSION_MINOR 0) set(RTTY_VERSION_PATCH 2) +option(KCP_SUPPORT "KCP support" ON) + # Check the third party Libraries find_package(Libev REQUIRED) -aux_source_directory(. SRCS) aux_source_directory(log SRCS) aux_source_directory(buffer SRCS) +add_subdirectory(ssl) + +list(APPEND SRCS main.c rtty.c net.c utils.c file.c filectl.c command.c http.c) + +if(KCP_SUPPORT) + if (NOT SSL_SUPPORT) + message(WARNING "SSL Not enabled, disable KCP") + set(KCP_SUPPORT OFF) + else() + list(APPEND SRCS kcp.c crc32.c crypt.c) + endif() +endif() + add_executable(rtty ${SRCS}) target_compile_definitions(rtty PRIVATE _GNU_SOURCE) target_compile_options(rtty PRIVATE -O -Wall -Werror --std=gnu99) target_include_directories(rtty PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/buffer ${LIBEV_INCLUDE_DIR}) target_link_libraries(rtty PRIVATE ${LIBEV_LIBRARY} util crypt m) -add_subdirectory(ssl) - if(SSL_SUPPORT) + target_compile_definitions(rtty PRIVATE ${SSL_DEFINE}) target_link_libraries(rtty PRIVATE ${SSL_TARGET}) endif() +if(KCP_SUPPORT) + add_subdirectory(kcp) + target_link_libraries(rtty PRIVATE kcp) +endif() + # configure a header file to pass some of the CMake settings to the source code configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/src/config.h.in b/src/config.h.in index 93bc6da..ceb66c4 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -31,5 +31,6 @@ #define RTTY_VERSION_STRING "@RTTY_VERSION_MAJOR@.@RTTY_VERSION_MINOR@.@RTTY_VERSION_PATCH@" #cmakedefine SSL_SUPPORT +#cmakedefine KCP_SUPPORT #endif diff --git a/src/crc32.c b/src/crc32.c new file mode 100644 index 0000000..44759b4 --- /dev/null +++ b/src/crc32.c @@ -0,0 +1,120 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include + +#define IEEE 0xedb88320 + +#define slicing8_cutoff 16 + +typedef uint32_t table[256]; + +typedef struct slicing8_table { + table tables[8]; +} slicing8_table; + +static slicing8_table tab; + +static void simple_populate_table(uint32_t poly, table t) +{ + int i, j; + + for (i = 0; i < 256; i++) { + uint32_t crc = i; + for (j = 0; j < 8; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ poly; + } else { + crc >>= 1; + } + } + t[i] = crc; + } +} + +static void slicing_make_table(uint32_t poly) +{ + int i, j; + + simple_populate_table(poly, tab.tables[0]); + + for (i = 0; i < 256; i++) { + uint32_t crc = tab.tables[0][i]; + for (j = 1; j < 8; j++) { + crc = tab.tables[0][crc & 0xFF] ^ (crc >> 8); + tab.tables[j][i] = crc; + } + } +} + +static uint32_t simple_update(uint32_t crc, const table tab, const uint8_t *p, size_t len) +{ + size_t i; + + crc = ~crc; + + for (i = 0; i < len; i++) + crc = tab[(uint8_t)crc ^ p[i]] ^ (crc >> 8); + + return ~crc; +} + +uint32_t crc32_checksum_ieee(const uint8_t* p, size_t len) +{ + static bool inited; + uint32_t crc = 0; + + if (!inited) { + inited = true; + slicing_make_table(IEEE); + } + + if (len >= slicing8_cutoff) { + crc = ~crc; + while (len >= 8) { + uint32_t n; + memcpy(&n, p, sizeof(n)); + crc ^= n; + crc = tab.tables[0][p[7]] ^ + tab.tables[1][p[6]] ^ + tab.tables[2][p[5]] ^ + tab.tables[3][p[4]] ^ + tab.tables[4][crc >> 24] ^ + tab.tables[5][(crc >> 16) & 0xFF] ^ + tab.tables[6][(crc >> 8) & 0xFF] ^ + tab.tables[7][crc & 0xFF]; + p += 8; + len -= 8; + } + crc = ~crc; + } + + if (len == 0) + return crc; + + return simple_update(crc, tab.tables[0], p, len); +} diff --git a/src/crc32.h b/src/crc32.h new file mode 100644 index 0000000..77e8703 --- /dev/null +++ b/src/crc32.h @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef RTTY_CRC32_H +#define RTTY_CRC32_H + +#include +#include + +uint32_t crc32_checksum_ieee(const uint8_t *p, size_t len); + +#endif diff --git a/src/crypt.c b/src/crypt.c new file mode 100644 index 0000000..6331a31 --- /dev/null +++ b/src/crypt.c @@ -0,0 +1,268 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENSSL +#include +#elif defined(HAVE_WOLFSSL) +#include +#include +#else +#include +#include +#include +#endif + +#include "crypt.h" + +void *cipher_init_ctx(const char *password) +{ + uint8_t key[32]; + +#if defined(HAVE_OPENSSL) || defined(HAVE_WOLFSSL) + EVP_CIPHER_CTX *cipher = EVP_CIPHER_CTX_new(); + + PKCS5_PBKDF2_HMAC(password, strlen(password), NULL, 0, 1024, EVP_sha256(), 32, key); + EVP_EncryptInit(cipher, EVP_aes_256_ecb(), key, NULL); + + return cipher; +#else + mbedtls_aes_context *aes_ctx; + mbedtls_md_context_t md_ctx; + + mbedtls_md_init(&md_ctx); + mbedtls_md_setup(&md_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1); + mbedtls_pkcs5_pbkdf2_hmac(&md_ctx, (const uint8_t *)password, strlen(password), + NULL, 0, 1024, 32, key); + mbedtls_md_free(&md_ctx); + + aes_ctx = malloc(sizeof(mbedtls_aes_context)); + + mbedtls_aes_init(aes_ctx); + mbedtls_aes_setkey_enc(aes_ctx, key, 256); + + return aes_ctx; +#endif +} + +int get_random_bytes(uint8_t *out, size_t len) +{ + int ret = 0; + int fd; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + + if (read(fd, out, len) < 0) + ret = -1; + + close(fd); + + return ret; +} + +static uint8_t initial_vector[] = {167, 115, 79, 156, 18, 172, 27, 1, 164, 21, 242, 193, 252, 120, 230, 107}; + +static void xor_bytes(uint8_t *dst, const uint8_t *a, const uint8_t *b, size_t n) +{ + for (size_t i = 0; i < n; i++) { + dst[i] = a[i] ^ b[i]; + } +} + +static inline void aes_block_encrypt(void *ctx, uint8_t *dst, const uint8_t *src) +{ +#if defined(HAVE_OPENSSL) || defined(HAVE_WOLFSSL) + int out_len; + EVP_EncryptUpdate(ctx, dst, &out_len, src, 16); +#else + mbedtls_aes_crypt_ecb(ctx, MBEDTLS_AES_ENCRYPT, src, dst); +#endif +} + +void encrypt16(void *ctx, uint8_t *dst, const uint8_t *src, size_t src_len) +{ + size_t n = src_len / 16; + size_t repeat = n / 8; + size_t left = n % 8; + size_t remaining; + size_t base = 0; + uint8_t tbl[16]; + + aes_block_encrypt(ctx, tbl, initial_vector); + + for (size_t i = 0; i < repeat; i++) { + const uint8_t *s = src + base; + uint8_t *d = dst + base; + for (int j = 0; j < 8; j++) { + xor_bytes(d + j*16, s + j*16, tbl, 16); + aes_block_encrypt(ctx, tbl, d + j * 16); + } + base += 128; + } + + switch (left) { + case 7: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 6: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 5: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 4: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 3: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 2: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 1: + xor_bytes(dst + base, src + base, tbl, 16); + aes_block_encrypt(ctx, tbl, dst + base); + base += 16; + case 0: + remaining = src_len - base; + if (remaining > 0) + xor_bytes(dst + base, src + base, tbl, remaining); + break; + } +} + +void decrypt16(void *ctx, uint8_t *dst, const uint8_t *src, size_t src_len) +{ + size_t n = src_len / 16; + size_t repeat = n / 8; + size_t left = n % 8; + size_t base = 0; + uint8_t buf[32]; + uint8_t *tbl = buf; + uint8_t *next = buf + 16; + uint8_t *temp_ptr; + + aes_block_encrypt(ctx, tbl, initial_vector); + + for (size_t i = 0; i < repeat; i++) { + const uint8_t *s = src + base; + uint8_t *d = dst + base; + + aes_block_encrypt(ctx, next, s); + xor_bytes(d, s, tbl, 16); + + aes_block_encrypt(ctx, tbl, s + 16); + xor_bytes(d + 16, s + 16, next, 16); + + aes_block_encrypt(ctx, next, s + 32); + xor_bytes(d + 32, s + 32, tbl, 16); + + aes_block_encrypt(ctx, tbl, s + 48); + xor_bytes(d + 48, s + 48, next, 16); + + aes_block_encrypt(ctx, next, s + 64); + xor_bytes(d + 64, s + 64, tbl, 16); + + aes_block_encrypt(ctx, tbl, s + 80); + xor_bytes(d + 80, s + 80, next, 16); + + aes_block_encrypt(ctx, next, s + 96); + xor_bytes(d + 96, s + 96, tbl, 16); + + aes_block_encrypt(ctx, tbl, s + 112); + xor_bytes(d + 112, s + 112, next, 16); + + base += 128; + } + + switch (left) { + case 7: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 6: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 5: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 4: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 3: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 2: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 1: + aes_block_encrypt(ctx, next, src + base); + xor_bytes(dst + base, src + base, tbl, 16); + temp_ptr = tbl; + tbl = next; + next = temp_ptr; + base += 16; + case 0: + size_t remaining = src_len - base; + if (remaining > 0) + xor_bytes(dst + base, src + base, tbl, remaining); + break; + } +} diff --git a/src/crypt.h b/src/crypt.h new file mode 100644 index 0000000..2d1504c --- /dev/null +++ b/src/crypt.h @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef RTTY_CRYPT_H +#define RTTY_CRYPT_H + +#include + +void *cipher_init_ctx(const char *password); + +int get_random_bytes(uint8_t *out, size_t len); + +void encrypt16(void *ctx, uint8_t *dst, const uint8_t *src, size_t src_len); +void decrypt16(void *ctx, uint8_t *dst, const uint8_t *src, size_t src_len); + +#endif diff --git a/src/kcp b/src/kcp new file mode 160000 index 0000000..f4f3a89 --- /dev/null +++ b/src/kcp @@ -0,0 +1 @@ +Subproject commit f4f3a89cc632647dabdcb146932d2afd5591e62e diff --git a/src/kcp.c b/src/kcp.c new file mode 100644 index 0000000..2eb9cc4 --- /dev/null +++ b/src/kcp.c @@ -0,0 +1,241 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include "log/log.h" +#include "crc32.h" +#include "crypt.h" +#include "rtty.h" + +#define NONCE_SIZE 16 +#define CRC_SIZE 4 +#define CRYPT_HEADER_SIZE (NONCE_SIZE + CRC_SIZE) + +#ifdef KCP_SUPPORT + +void rtty_kcp_init_cipher(struct rtty_kcp *kcp) +{ + if (!kcp->password) + return; + + kcp->cipher = cipher_init_ctx(kcp->password); +} + +static uint32_t rtty_kcp_now(struct rtty *rtty) +{ + long now = ev_now(rtty->loop) * 1000; + return now & 0xffffffff; +} + +void rtty_kcp_check(struct rtty *rtty) +{ + uint32_t now = rtty_kcp_now(rtty); + uint32_t next; + long delay; + + next = ikcp_check(rtty->kcp.kcp, now); + delay = next - now; + + if (delay <= 0) + delay = 1; + + ev_timer_set(&rtty->kcp.tmr, 0, delay / 1000.0); + ev_timer_again(rtty->loop, &rtty->kcp.tmr); +} + +static int rtty_kcp_encrypt(struct rtty_kcp *kcp, uint8_t *dst, const uint8_t *src, int srclen) +{ + uint32_t crc32; + + get_random_bytes(dst, NONCE_SIZE); + + crc32 = crc32_checksum_ieee(src, srclen); + + crc32 = htole32(crc32); + + memcpy(dst + NONCE_SIZE, &crc32, CRC_SIZE); + memcpy(dst + CRYPT_HEADER_SIZE, src, srclen); + + encrypt16(kcp->cipher, dst, dst, srclen + CRYPT_HEADER_SIZE); + + return CRYPT_HEADER_SIZE + srclen; +} + +static int rtty_kcp_decrypt(struct rtty_kcp *kcp, uint8_t *dst, const uint8_t *src, int srclen) +{ + uint32_t crc32_your, crc32_our; + + if (srclen < CRYPT_HEADER_SIZE) { + log_debug("data length too short\n"); + return -1; + } + + decrypt16(kcp->cipher, dst, src, srclen); + + crc32_our = crc32_checksum_ieee(src + CRYPT_HEADER_SIZE, srclen - CRYPT_HEADER_SIZE); + + memcpy(&crc32_your, dst + NONCE_SIZE, CRC_SIZE); + + if (le32toh(crc32_your) !=crc32_our) { + log_debug("crc32 error\n"); + return -1; + } + + return srclen; +} + +static int udp_output(const char *buf, int len, ikcpcb *kcp, void *user) +{ + struct rtty *rtty = user; + + if (rtty->kcp.password) { + uint8_t dst[1500]; + int ret= rtty_kcp_encrypt(&rtty->kcp, dst, (const uint8_t *)buf, len); + return send(rtty->sock, dst, ret, 0); + } + + return send(rtty->sock, buf, len, 0); +} + +static void kcp_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents) +{ + struct rtty *rtty = container_of(w, struct rtty, kcp.tmr); + uint32_t now = rtty_kcp_now(rtty); + + ikcp_update(rtty->kcp.kcp, now); + rtty_kcp_check(rtty); +} + +int rtty_kcp_init(struct rtty *rtty) +{ + ikcpcb *kcp; + + if (!rtty->kcp.on) + return 0; + + kcp = ikcp_create(random(), rtty); + if (!kcp) { + log_err("malloc: %s\n", strerror(errno)); + return -1; + } + + ikcp_setoutput(kcp, udp_output); + + ikcp_nodelay(kcp, rtty->kcp.nodelay, rtty->kcp.interval, + rtty->kcp.resend, rtty->kcp.nc); + + ikcp_wndsize(kcp, rtty->kcp.sndwnd, rtty->kcp.rcvwnd); + + ikcp_setmtu(kcp, rtty->kcp.mtu); + + ev_timer_init(&rtty->kcp.tmr, kcp_timer_cb, 1.0, 0); + + rtty->kcp.kcp = kcp; + + return 0; +} + +void rtty_kcp_release(struct rtty *rtty) +{ + if (rtty->kcp.kcp) + return; + + ikcp_release(rtty->kcp.kcp); + rtty->kcp.kcp = NULL; +} + +int rtty_kcp_read(struct rtty *rtty, int fd) +{ + char buf[1500]; + char *in = buf; + int ret; + + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) { + log_err("socket read error: %s\n", strerror(errno)); + return -1; + } + + if (rtty->kcp.password) { + ret = rtty_kcp_decrypt(&rtty->kcp, (uint8_t *)buf, (const uint8_t *)buf, ret); + if (ret < 0) + return -1; + in = buf + CRYPT_HEADER_SIZE; + ret -= CRYPT_HEADER_SIZE; + } + + ret = ikcp_input(rtty->kcp.kcp, in, ret); + if (ret < 0) { + log_err("ikcp_input fail: %d\n", ret); + return -1; + } + + while (true) { + ret = ikcp_recv(rtty->kcp.kcp, buf, sizeof(buf)); + if (ret < 0) + break; + buffer_put_data(&rtty->rb, buf, ret); + } + + rtty_kcp_check(rtty); + + return 0; +} + +int rtty_kcp_write(struct rtty *rtty, int fd) +{ + struct buffer *b = &rtty->wb; + ikcpcb *kcp = rtty->kcp.kcp; + size_t mss = kcp->mss; + int ret; + + if (rtty->kcp.password) + mss -= CRYPT_HEADER_SIZE; + + while (true) { + size_t len = buffer_length(b); + if (len == 0) + break; + + if (len > mss) + len = mss; + + ret = ikcp_send(kcp, buffer_data(b), len); + if (ret < 0) { + log_err("ikcp_send fail: %d\n", ret); + return -1; + } + + buffer_pull(b, NULL, ret); + } + + rtty_kcp_check(rtty); + + return 0; +} + +#endif diff --git a/src/kcp.h b/src/kcp.h new file mode 100644 index 0000000..b9e6aad --- /dev/null +++ b/src/kcp.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2019 Jianhui Zhao + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef RTTY_KCP_H +#define RTTY_KCP_H + +#include +#include + +#include "kcp/ikcp.h" + +struct rtty; + +struct rtty_kcp { + bool on; + + ikcpcb *kcp; + + struct ev_timer tmr; + + const char *password; + void *cipher; + + bool nodelay; + int interval; + int resend; + bool nc; + int sndwnd; + int rcvwnd; + int mtu; +}; + +void rtty_kcp_init_cipher(struct rtty_kcp *kcp); +void rtty_kcp_check(struct rtty *rtty); +int rtty_kcp_init(struct rtty *rtty); +void rtty_kcp_release(struct rtty *rtty); +int rtty_kcp_read(struct rtty *rtty, int fd); +int rtty_kcp_write(struct rtty *rtty, int fd); + +#endif diff --git a/src/main.c b/src/main.c index 2cc3350..a406d3e 100644 --- a/src/main.c +++ b/src/main.c @@ -34,7 +34,18 @@ #include "rtty.h" enum { - LONG_OPT_HELP = 1 + LONG_OPT_HELP = 1, +#ifdef KCP_SUPPORT + LONG_OPT_KCP, + LONG_OPT_KCP_NODELAY, + LONG_OPT_KCP_INTERVAL, + LONG_OPT_KCP_RESEND, + LONG_OPT_KCP_NC, + LONG_OPT_KCP_SNDWND, + LONG_OPT_KCP_RCVWND, + LONG_OPT_KCP_MTU, + LONG_OPT_KCP_KEY, +#endif }; #ifdef SSL_SUPPORT @@ -70,6 +81,17 @@ static struct option long_options[] = { {"port", required_argument, NULL, 'p'}, {"description", required_argument, NULL, 'd'}, {"token", required_argument, NULL, 't'}, +#ifdef KCP_SUPPORT + {"kcp", no_argument, NULL, LONG_OPT_KCP}, + {"kcp-nodelay", no_argument, NULL, LONG_OPT_KCP_NODELAY}, + {"kcp-interval", required_argument, NULL, LONG_OPT_KCP_INTERVAL}, + {"kcp-resend", required_argument, NULL, LONG_OPT_KCP_RESEND}, + {"kcp-nc", no_argument, NULL, LONG_OPT_KCP_NC}, + {"kcp-sndwnd", required_argument, NULL, LONG_OPT_KCP_SNDWND}, + {"kcp-rcvwnd", required_argument, NULL, LONG_OPT_KCP_RCVWND}, + {"kcp-mtu", required_argument, NULL, LONG_OPT_KCP_MTU}, + {"kcp-key", required_argument, NULL, LONG_OPT_KCP_KEY}, +#endif #ifdef SSL_SUPPORT {"cacert", required_argument, NULL, 'C'}, {"insecure", no_argument, NULL, 'x'}, @@ -92,6 +114,17 @@ static void usage(const char *prog) " -d, --description=string Add a description to the device(Maximum 126 bytes)\n" " -a Auto reconnect to the server\n" " -i number Set heartbeat interval in seconds(Default is 30s)\n" +#ifdef KCP_SUPPORT + " --kcp Using KCP protocol\n" + " --kcp-nodelay Whether enable nodelay mode for KCP\n" + " --kcp-interval number KCP protocol internal work interval(Default is 100ms)\n" + " --kcp-resend number Fast retransmission mode for KCP\n" + " --kcp-nc Whether to turn off flow control for KCP\n" + " --kcp-sndwnd number Maximum send window for KCP\n" + " --kcp-rcvwnd number Maximum receive window for KCP\n" + " --kcp-mtu number Maximum transmission unit for KCP\n" + " --kcp-key Key used to encrypt data\n" +#endif #ifdef SSL_SUPPORT " -s SSL on\n" " -C, --cacert CA certificate to verify peer against\n" @@ -129,7 +162,10 @@ int main(int argc, char **argv) .host = "localhost", .port = 5912, .loop = loop, - .sock = -1 + .sock = -1, +#ifdef KCP_SUPPORT + .kcp.interval = -1 +#endif }; #ifdef SSL_SUPPORT bool has_cacert = false; @@ -194,6 +230,35 @@ int main(int argc, char **argv) case 'a': rtty.reconnect = true; break; +#ifdef KCP_SUPPORT + case LONG_OPT_KCP: + rtty.kcp.on = true; + break; + case LONG_OPT_KCP_NODELAY: + rtty.kcp.nodelay = true; + break; + case LONG_OPT_KCP_INTERVAL: + rtty.kcp.interval = atoi(optarg); + break; + case LONG_OPT_KCP_RESEND: + rtty.kcp.resend = atoi(optarg); + break; + case LONG_OPT_KCP_NC: + rtty.kcp.nc = true; + break; + case LONG_OPT_KCP_SNDWND: + rtty.kcp.sndwnd = atoi(optarg); + break; + case LONG_OPT_KCP_RCVWND: + rtty.kcp.rcvwnd = atoi(optarg); + break; + case LONG_OPT_KCP_MTU: + rtty.kcp.mtu = atoi(optarg); + break; + case LONG_OPT_KCP_KEY: + rtty.kcp.password = optarg; + break; +#endif #ifdef SSL_SUPPORT case 's': rtty.ssl_on = true; diff --git a/src/net.c b/src/net.c index c64aa5a..3ec2ad6 100644 --- a/src/net.c +++ b/src/net.c @@ -167,22 +167,17 @@ err: return -1; } -int tcp_connect(struct ev_loop *loop, const char *host, int port, - void (*on_connected)(int sock, void *arg), void *arg) +static int getaddr_by_name(int socktype, const char *host, int port, + struct sockaddr *dest, int *addrlen) { - struct sockaddr *addr = NULL; struct addrinfo *result, *rp; struct addrinfo hints = { .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, + .ai_socktype = socktype, .ai_flags = AI_ADDRCONFIG }; - int sock = -1; - int addrlen; int ret; - log_debug("connecting to %s:%d\n", host, port); - ret = getaddrinfo(host, port2str(port), &hints, &result); if (ret) { if (ret == EAI_SYSTEM) { @@ -194,22 +189,67 @@ int tcp_connect(struct ev_loop *loop, const char *host, int port, return -1; } + ret = -1; + for (rp = result; rp != NULL; rp = rp->ai_next) { if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) { - addr = rp->ai_addr; - addrlen = rp->ai_addrlen; + memcpy(dest, rp->ai_addr, rp->ai_addrlen); + *addrlen = rp->ai_addrlen; + ret = 0; break; } } - if (!addr) { + freeaddrinfo(result); + + return ret; +} + +int tcp_connect(struct ev_loop *loop, const char *host, int port, + void (*on_connected)(int sock, void *arg), void *arg) +{ + struct sockaddr addr; + int addrlen; + + log_debug("connecting to %s:%d\n", host, port); + + if (getaddr_by_name(SOCK_STREAM, host, port, &addr, &addrlen)) { log_err("getaddrinfo failed: Not found addr\n"); - goto free_addrinfo; + return -1; } - sock = tcp_connect_sockaddr(loop, addr, addrlen, on_connected, arg); + return tcp_connect_sockaddr(loop, &addr, addrlen, on_connected, arg); +} + +int udp_connect(struct ev_loop *loop, const char *host, int port, + void (*on_connected)(int sock, void *arg), void *arg) +{ + struct sockaddr addr; + int addrlen; + int sock; + int ret; + + log_debug("connecting to %s:%d\n", host, port); + + if (getaddr_by_name(SOCK_DGRAM, host, port, &addr, &addrlen)) { + log_err("getaddrinfo failed: Not found addr\n"); + return -1; + } + + sock = socket(addr.sa_family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (sock < 0) { + log_err("create socket failed: %s\n", strerror(errno)); + return -1; + } + + ret = connect(sock, &addr, addrlen); + if (ret < 0) { + close(sock); + log_err("connect '%s:%d' failed: %s\n", host, port, strerror(errno)); + return -1; + } + + on_connected(sock, arg); -free_addrinfo: - freeaddrinfo(result); return sock; } diff --git a/src/net.h b/src/net.h index 1a40d7c..30063a3 100644 --- a/src/net.h +++ b/src/net.h @@ -34,4 +34,7 @@ int tcp_connect(struct ev_loop *loop, const char *host, int port, int tcp_connect_sockaddr(struct ev_loop *loop, const struct sockaddr *addr, socklen_t addrlen, void (*on_connected)(int sock, void *arg), void *arg); +int udp_connect(struct ev_loop *loop, const char *host, int port, + void (*on_connected)(int sock, void *arg), void *arg); + #endif diff --git a/src/rtty.c b/src/rtty.c index 5107192..acc040b 100644 --- a/src/rtty.c +++ b/src/rtty.c @@ -302,6 +302,10 @@ void rtty_exit(struct rtty *rtty) del_tty(tty); } +#ifdef KCP_SUPPORT + rtty_kcp_release(rtty); +#endif + #ifdef SSL_SUPPORT if (rtty->ssl) { ssl_session_free(rtty->ssl); @@ -608,10 +612,18 @@ static void on_net_read(struct ev_loop *loop, struct ev_io *w, int revents) goto err; #endif } else { - ret = buffer_put_fd(&rtty->rb, w->fd, 4096, &eof); - if (ret < 0) { - log_err("socket read error: %s\n", strerror(errno)); - goto err; +#ifdef KCP_SUPPORT + if (rtty->kcp.on) { + if (rtty_kcp_read(rtty, w->fd)) + goto err; + } else +#endif + { + ret = buffer_put_fd(&rtty->rb, w->fd, 4096, &eof); + if (ret < 0) { + log_err("socket read error: %s\n", strerror(errno)); + goto err; + } } } @@ -659,10 +671,18 @@ static void on_net_write(struct ev_loop *loop, struct ev_io *w, int revents) buffer_pull(b, NULL, ret); #endif } else { - ret = buffer_pull_to_fd(&rtty->wb, w->fd, -1); - if (ret < 0) { - log_err("socket write error: %s\n", strerror(errno)); - goto err; +#ifdef KCP_SUPPORT + if (rtty->kcp.on) { + if (rtty_kcp_write(rtty, w->fd)) + goto err; + } else +#endif + { + ret = buffer_pull_to_fd(&rtty->wb, w->fd, -1); + if (ret < 0) { + log_err("socket write error: %s\n", strerror(errno)); + goto err; + } } } @@ -688,12 +708,22 @@ static void on_net_connected(int sock, void *arg) rtty->sock = sock; +#ifdef KCP_SUPPORT + if (rtty_kcp_init(rtty)) { + ev_break(rtty->loop, EVBREAK_ALL); + return; + } +#endif + ev_io_init(&rtty->ior, on_net_read, sock, EV_READ); ev_io_start(rtty->loop, &rtty->ior); ev_io_init(&rtty->iow, on_net_write, sock, EV_WRITE); if (rtty->ssl_on) { +#ifdef KCP_SUPPORT + assert(!rtty->kcp.on); +#endif #ifdef SSL_SUPPORT rtty->ssl = ssl_session_new(rtty->ssl_ctx, sock); if (!rtty->ssl) { @@ -768,12 +798,23 @@ static void rtty_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents) int rtty_start(struct rtty *rtty) { + int ret; + rtty_run_state(RTTY_STATE_DISCONNECTED); ev_init(&rtty->tmr, rtty_timer_cb); - if (tcp_connect(rtty->loop, rtty->host, rtty->port, on_net_connected, rtty) < 0 - && !rtty->reconnect) +#ifdef KCP_SUPPORT + if (rtty->kcp.on) { + rtty_kcp_init_cipher(&rtty->kcp); + ret = udp_connect(rtty->loop, rtty->host, rtty->port, on_net_connected, rtty); + } else +#endif + { + ret = tcp_connect(rtty->loop, rtty->host, rtty->port, on_net_connected, rtty); + } + + if (ret < 0 && !rtty->reconnect) return -1; INIT_LIST_HEAD(&rtty->ttys); diff --git a/src/rtty.h b/src/rtty.h index 8d120cd..a8d5776 100644 --- a/src/rtty.h +++ b/src/rtty.h @@ -37,6 +37,10 @@ #include "ssl/ssl.h" #endif +#ifdef KCP_SUPPORT +#include "kcp.h" +#endif + #define RTTY_PROTO_VER 5 #define RTTY_MAX_TTY 10 #define RTTY_HEARTBEAT_TIMEOUT 3.0 @@ -122,6 +126,9 @@ struct rtty { int ntty; /* tty number */ struct list_head ttys; struct list_head http_conns; +#ifdef KCP_SUPPORT + struct rtty_kcp kcp; +#endif }; int rtty_start(struct rtty *rtty);