From 38bdd0d983b1e0a1d9fc6fb3bfcfb892e22b9ba7 Mon Sep 17 00:00:00 2001 From: ithewei Date: Mon, 27 Jun 2022 00:35:48 +0800 Subject: [PATCH 1/2] Add examples/kcptun --- Makefile | 8 + README-CN.md | 1 + README.md | 1 + examples/CMakeLists.txt | 16 ++ examples/kcptun/client/main.cpp | 402 ++++++++++++++++++++++++++++++++ examples/kcptun/server/main.cpp | 362 ++++++++++++++++++++++++++++ examples/kcptun/smux/smux.cpp | 79 +++++++ examples/kcptun/smux/smux.h | 138 +++++++++++ 8 files changed, 1007 insertions(+) create mode 100644 examples/kcptun/client/main.cpp create mode 100644 examples/kcptun/server/main.cpp create mode 100644 examples/kcptun/smux/smux.cpp create mode 100644 examples/kcptun/smux/smux.h diff --git a/Makefile b/Makefile index a875f99ff..6b216afed 100644 --- a/Makefile +++ b/Makefile @@ -191,6 +191,14 @@ mqtt_pub: prepare mqtt_client_test: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) mqtt" SRCS="examples/mqtt/mqtt_client_test.cpp" +kcptun: kcptun_client kcptun_server + +kcptun_client: prepare + $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) examples/kcptun/smux examples/kcptun/client" + +kcptun_server: prepare + $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS) examples/kcptun/smux examples/kcptun/server" + jsonrpc: jsonrpc_client jsonrpc_server jsonrpc_client: prepare diff --git a/README-CN.md b/README-CN.md index c196a05c8..8317a66e8 100644 --- a/README-CN.md +++ b/README-CN.md @@ -429,6 +429,7 @@ int main(int argc, char** argv) { - HTTP客户端: [examples/http_client_test.cpp](examples/http_client_test.cpp) - WebSocket服务端: [examples/websocket_server_test.cpp](examples/websocket_server_test.cpp) - WebSocket客户端: [examples/websocket_client_test.cpp](examples/websocket_client_test.cpp) +- kcptun隧道: [examples/kcptun](examples/kcptun) - protobufRPC示例: [examples/protorpc](examples/protorpc) - Qt中使用libhv示例: [hv-projects/QtDemo](https://github.com/hv-projects/QtDemo) diff --git a/README.md b/README.md index 87d728d53..bd1a3f0c3 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ int main(int argc, char** argv) { - [examples/http_client_test.cpp](examples/http_client_test.cpp) - [examples/websocket_server_test.cpp](examples/websocket_server_test.cpp) - [examples/websocket_client_test.cpp](examples/websocket_client_test.cpp) +- [examples/kcptun](examples/kcptun) - [examples/protorpc](examples/protorpc) - [hv-projects/QtDemo](https://github.com/hv-projects/QtDemo) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 327c3b29d..e02eb46de 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -61,6 +61,22 @@ add_executable(jsonrpc_server jsonrpc/jsonrpc_server.c jsonrpc/cJSON.c) target_compile_definitions(jsonrpc_server PRIVATE CJSON_HIDE_SYMBOLS) target_link_libraries(jsonrpc_server ${HV_LIBRARIES}) +if(WITH_KCP) + glob_headers_and_sources(KCPTUN_SMUX_FILES kcptun/smux) + glob_headers_and_sources(KCPTUN_CLIENT_FILES kcptun/client) + glob_headers_and_sources(KCPTUN_SERVER_FILES kcptun/server) + + # kcptun_client + add_executable(kcptun_client ${KCPTUN_SMUX_FILES} ${KCPTUN_CLIENT_FILES}) + target_link_libraries(kcptun_client ${HV_LIBRARIES}) + + # kcptun_server + add_executable(kcptun_server ${KCPTUN_SMUX_FILES} ${KCPTUN_SERVER_FILES}) + target_link_libraries(kcptun_server ${HV_LIBRARIES}) + + list(APPEND EXAMPLES kcptun_client kcptun_server) +endif() + if(WITH_EVPP) include_directories(../cpputil ../evpp) diff --git a/examples/kcptun/client/main.cpp b/examples/kcptun/client/main.cpp new file mode 100644 index 000000000..d6adb0b00 --- /dev/null +++ b/examples/kcptun/client/main.cpp @@ -0,0 +1,402 @@ +/* + * kcptun client + * + * @build: ./configure --with-kcp && make clean && make kcptun examples + * @tcp_server: bin/tcp_echo_server 1234 + * @kcptun_server: bin/kcptun_server -l :4000 -t 127.0.0.1:1234 + * @kcptun_client: bin/kcptun_client -l :8388 -r 127.0.0.1:4000 + * @tcp_client: bin/nc 127.0.0.1 8388 + * > hello + * < hello + */ + +#define WITH_KCP 1 +#include "hversion.h" +#include "hmain.h" +#include "hsocket.h" +#include "hloop.h" +#include "hthread.h" + +#include "../smux/smux.h" + +// config +static const char* localaddr = ":8388"; +static const char* remoteaddr = "127.0.0.1:4000"; +static const char* mode = "fast"; +static int mtu = 1350; +static int sndwnd = 128; +static int rcvwnd = 512; + +// short options +static const char options[] = "hvdl:r:m:"; +// long options +static const option_t long_options[] = { + {'h', "help", NO_ARGUMENT}, + {'v', "version", NO_ARGUMENT}, + {'d', "daemon", NO_ARGUMENT}, + {'l', "localaddr", REQUIRED_ARGUMENT}, + {'r', "remoteaddr", REQUIRED_ARGUMENT}, + {'m', "mode", REQUIRED_ARGUMENT}, + { 0, "mtu", REQUIRED_ARGUMENT}, + { 0, "sndwnd", REQUIRED_ARGUMENT}, + { 0, "rcvwnd", REQUIRED_ARGUMENT}, +}; + +static const char detail_options[] = R"( + -h|--help Print this information + -v|--version Print version + -d|--daemon Daemonize + -l|--localaddr value local listen address (default: ":8388") + -r|--remoteaddr value kcp server address (default: "127.0.0.1:4000") + -m|--mode value profiles: fast3, fast2, fast, normal (default: "fast") + --mtu value set maximum transmission unit for UDP packets (default: 1350) + --sndwnd value set send window size(num of packets) (default: 128) + --rcvwnd value set receive window size(num of packets) (default: 512) +)"; + +static void print_version() { + printf("%s version %s\n", g_main_ctx.program_name, hv_compile_version()); +} + +static void print_help() { + printf("Usage: %s [%s]\n", g_main_ctx.program_name, options); + printf("Options:\n%s\n", detail_options); +} + +static char listen_host[64] = "0.0.0.0"; +static int listen_port = 8388; +static hio_t* listen_io = NULL; + +static kcp_setting_t s_kcp_setting; +static char kcp_host[64] = "127.0.0.1"; +static int kcp_port = 4000; +static hio_t* kcp_io = NULL; + +static smux_config_t smux_config; +static smux_session_t smux_session; +static smux_stream_t smux_stream0; + +static int verbose = 1; + +typedef struct kcp_ctx_s { + smux_frame_t frame; + uint16_t want_readbytes; +} kcp_ctx_t; + +/* workflow: + * + * hloop_create_tcp_server -> + * on_accept -> smux_session_open_stream -> + * on_recv -> hio_write(kcp_io) -> + * on_close -> smux_session_close_stream + * + * hloop_create_udp_client -> hio_set_kcp -> + * on_recvfrom -> smux_session_get_stream -> hio_write(stream_io) + * + */ + +static void send_hearbeat(htimer_t* timer) { + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(timer); + if (smux_stream == NULL) return; + // NOP + int packlen = smux_stream_output(smux_stream, SMUX_CMD_NOP); + if (packlen > 0) { + // printf("NOP %d\n", packlen); + hio_write(kcp_io, smux_stream->wbuf.base, packlen); + } +} + +static void on_close(hio_t* io) { + // printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io)); + if (verbose) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printf("disconnected connfd=%d [%s] <= [%s]\n", hio_fd(io), + SOCKADDR_STR(hio_localaddr(io), localaddrstr), + SOCKADDR_STR(hio_peeraddr(io), peeraddrstr)); + } + + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(io); + if (smux_stream == NULL) return; + + // FIN + int packlen = smux_stream_output(smux_stream, SMUX_CMD_FIN); + if (packlen > 0) { + // printf("FIN %d\n", packlen); + hio_write(kcp_io, smux_stream->wbuf.base, packlen); + } + + // kill timer + if (smux_stream->timer) { + htimer_del(smux_stream->timer); + smux_stream->timer = NULL; + } + + // free buffer + HV_FREE(smux_stream->rbuf.base); + HV_FREE(smux_stream->wbuf.base); + + smux_session_close_stream(&smux_session, smux_stream->stream_id); + hevent_set_userdata(io, NULL); +} + +static void on_recv(hio_t* io, void* buf, int readbytes) { + // printf("on_recv %.*s \n", readbytes, (char*)buf); + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(io); + if (smux_stream == NULL) return; + + // PSH + smux_frame_t frame; + smux_frame_init(&frame); + frame.head.sid = smux_stream->stream_id; + frame.head.cmd = SMUX_CMD_PSH; + frame.head.length = readbytes; + frame.data = (const char*)buf; + int packlen = smux_frame_pack(&frame, smux_stream->wbuf.base, smux_stream->wbuf.len); + if (packlen > 0) { + // printf("PSH %d\n", packlen); + int nwrite = hio_write(kcp_io, smux_stream->wbuf.base, packlen); + // printf("PSH ret=%d\n", nwrite); + } +} + +static void on_accept(hio_t* io) { + // printf("on_accept connfd=%d\n", hio_fd(io)); + if (verbose) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io), + SOCKADDR_STR(hio_localaddr(io), localaddrstr), + SOCKADDR_STR(hio_peeraddr(io), peeraddrstr)); + } + + hio_setcb_close(io, on_close); + + smux_stream_t* smux_stream = smux_session_open_stream(&smux_session, 0, io); + // alloc buffer + smux_stream->rbuf.len = mtu; + smux_stream->wbuf.len = mtu; + HV_ALLOC(smux_stream->rbuf.base, smux_stream->rbuf.len); + HV_ALLOC(smux_stream->wbuf.base, smux_stream->wbuf.len); + hio_set_readbuf(io, smux_stream->rbuf.base, smux_config.max_frame_size); + /* + // set heartbeat timer + if (smux_config.keepalive_interval > 0) { + smux_stream->timer = htimer_add(hevent_loop(io), send_hearbeat, smux_config.keepalive_interval, INFINITE); + hevent_set_userdata(smux_stream->timer, smux_stream); + } + */ + hevent_set_userdata(io, smux_stream); + + // SYN + int packlen = smux_stream_output(smux_stream, SMUX_CMD_SYN); + if (packlen > 0) { + // printf("SYN %d\n", packlen); + hio_write(kcp_io, smux_stream->wbuf.base, packlen); + } + + hio_setcb_read(io, on_recv); + hio_read(io); +} + +static void on_kcp_recvfrom(hio_t* io, void* buf, int readbytes) { + // printf("on_kcp_recvfrom %d\n", readbytes); + + kcp_ctx_t* kcp_ctx = (kcp_ctx_t*)hevent_userdata(io); + smux_frame_t* frame = &kcp_ctx->frame; + if (kcp_ctx->want_readbytes > 0) { + frame->data = (const char*)buf; + frame->head.length = readbytes; + kcp_ctx->want_readbytes -= readbytes; + } + else { + smux_frame_init(frame); + int packlen = smux_frame_unpack(frame, buf, readbytes); + if (packlen < 0 || + frame->head.version > 2 || + frame->head.cmd > SMUX_CMD_UPD) { + fprintf(stderr, "smux_frame_unpack error: %d\n", packlen); + return; + } + int datalen = packlen - SMUX_HEAD_LENGTH; + if (datalen < frame->head.length) { + kcp_ctx->want_readbytes = frame->head.length - datalen; + frame->head.length = datalen; + } + } + // printf("smux sid=%u cmd=%d length=%d\n", frame->head.sid, (int)frame->head.cmd, (int)frame->head.length); + + smux_stream_t* smux_stream = smux_session_get_stream(&smux_session, kcp_ctx->frame.head.sid); + if (smux_stream == NULL) { + if (frame->head.sid != 0 && frame->head.cmd != SMUX_CMD_FIN) { + fprintf(stderr, "recvfrom invalid smux package!\n"); + } + return; + } + + switch (frame->head.cmd) { + case SMUX_CMD_SYN: + hio_setcb_read(smux_stream->io, on_recv); + hio_read(smux_stream->io); + break; + case SMUX_CMD_FIN: + hio_close(smux_stream->io); + break; + case SMUX_CMD_PSH: + hio_write(smux_stream->io, frame->data, frame->head.length); + break; + case SMUX_CMD_NOP: + break; + default: + break; + } +} + +int main(int argc, char** argv) { + if (argc < 2) { + print_help(); + exit(0); + } + + // g_main_ctx + main_ctx_init(argc, argv); + //int ret = parse_opt(argc, argv, options); + int ret = parse_opt_long(argc, argv, long_options, ARRAY_SIZE(long_options)); + if (ret != 0) { + print_help(); + exit(ret); + } + + // help + if (get_arg("h")) { + print_help(); + exit(0); + } + + // version + if (get_arg("v")) { + print_version(); + exit(0); + } + +#ifdef OS_UNIX + // daemon + if (get_arg("d")) { + // nochdir, noclose + int ret = daemon(1, 1); + if (ret != 0) { + printf("daemon error: %d\n", ret); + exit(-10); + } + } +#endif + + const char* arg = get_arg("l"); + if (arg) { + localaddr = arg; + } + + arg = get_arg("r"); + if (arg) { + remoteaddr = arg; + } + + arg = get_arg("m"); + if (arg) { + mode = arg; + } + + arg = get_arg("mtu"); + if (arg) { + mtu = atoi(arg); + } + + arg = get_arg("sndwnd"); + if (arg) { + sndwnd = atoi(arg); + } + + arg = get_arg("rcvwnd"); + if (arg) { + rcvwnd = atoi(arg); + } + + const char* pos = strchr(localaddr, ':'); + int len = 0; + if (pos) { + len = pos - localaddr; + if (len > 0) { + memcpy(listen_host, localaddr, len); + listen_host[len] = '\0'; + } + listen_port = atoi(pos + 1); + } + + pos = strchr(remoteaddr, ':'); + if (pos) { + len = pos - remoteaddr; + if (len > 0) { + memcpy(kcp_host, remoteaddr, len); + kcp_host[len] = '\0'; + } + kcp_port = atoi(pos + 1); + } + + if (strcmp(mode, "normal") == 0) { + kcp_setting_init_with_normal_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast") == 0) { + kcp_setting_init_with_fast_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast2") == 0) { + kcp_setting_init_with_fast2_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast3") == 0) { + kcp_setting_init_with_fast3_mode(&s_kcp_setting); + } else { + fprintf(stderr, "Unknown mode '%s'\n", mode); + exit(-20); + } + s_kcp_setting.conv = hv_getpid(); + s_kcp_setting.mtu = mtu; + s_kcp_setting.sndwnd = sndwnd; + s_kcp_setting.rcvwnd = rcvwnd; + + printf("smux version: 1\n"); + printf("%s:%d => %s:%d\n", listen_host, listen_port, kcp_host, kcp_port); + printf("mode: %s\n", mode); + printf("mtu: %d\n", mtu); + printf("sndwnd: %d rcvwnd: %d\n", sndwnd, rcvwnd); + + hloop_t* loop = hloop_new(0); + + listen_io = hloop_create_tcp_server(loop, listen_host, listen_port, on_accept); + if (listen_io == NULL) { + fprintf(stderr, "create tcp server error!\n"); + return -30; + } + + kcp_io = hloop_create_udp_client(loop, kcp_host, kcp_port); + if (kcp_io == NULL) { + fprintf(stderr, "create udp client error!\n"); + return -40; + } + hio_set_kcp(kcp_io, &s_kcp_setting); + kcp_ctx_t* kcp_ctx = NULL; + HV_ALLOC_SIZEOF(kcp_ctx); + hevent_set_userdata(kcp_io, kcp_ctx); + hio_setcb_read(kcp_io, on_kcp_recvfrom); + hio_read(kcp_io); + + // smux + smux_config.max_frame_size = 1024; + smux_session.next_stream_id = 1; + smux_stream0.stream_id = 0; + + // set heartbeat timer + if (smux_config.keepalive_interval > 0) { + htimer_t* timer = htimer_add(loop, send_hearbeat, smux_config.keepalive_interval, INFINITE); + hevent_set_userdata(timer, &smux_stream0); + } + + hloop_run(loop); + hloop_free(&loop); + return 0; +} diff --git a/examples/kcptun/server/main.cpp b/examples/kcptun/server/main.cpp new file mode 100644 index 000000000..235f4e104 --- /dev/null +++ b/examples/kcptun/server/main.cpp @@ -0,0 +1,362 @@ +/* + * kcptun server + * + * @build: ./configure --with-kcp && make clean && make kcptun examples + * @tcp_server: bin/tcp_echo_server 1234 + * @kcptun_server: bin/kcptun_server -l :4000 -t 127.0.0.1:1234 + * @kcptun_client: bin/kcptun_client -l :8388 -r 127.0.0.1:4000 + * @tcp_client: bin/nc 127.0.0.1 8388 + * > hello + * < hello + */ + +#define WITH_KCP 1 +#include "hversion.h" +#include "hmain.h" +#include "hsocket.h" +#include "hloop.h" + +#include "../smux/smux.h" + +// config +static const char* localaddr = ":4000"; +static const char* targetaddr = "127.0.0.1:8080"; +static const char* mode = "fast"; +static int mtu = 1350; +static int sndwnd = 1024; +static int rcvwnd = 1024; + +// short options +static const char options[] = "hvdl:t:m:"; +// long options +static const option_t long_options[] = { + {'h', "help", NO_ARGUMENT}, + {'v', "version", NO_ARGUMENT}, + {'d', "daemon", NO_ARGUMENT}, + {'l', "listen", REQUIRED_ARGUMENT}, + {'t', "target", REQUIRED_ARGUMENT}, + {'m', "mode", REQUIRED_ARGUMENT}, + { 0, "mtu", REQUIRED_ARGUMENT}, + { 0, "sndwnd", REQUIRED_ARGUMENT}, + { 0, "rcvwnd", REQUIRED_ARGUMENT}, +}; + +static const char detail_options[] = R"( + -h|--help Print this information + -v|--version Print version + -d|--daemon Daemonize + -l|--listen value kcp server listen address (default: ":4000") + -t|--target value target server address (default: "127.0.0.1:8080") + -m|--mode value profiles: fast3, fast2, fast, normal (default: "fast") + --mtu value set maximum transmission unit for UDP packets (default: 1350) + --sndwnd value set send window size(num of packets) (default: 1024) + --rcvwnd value set receive window size(num of packets) (default: 1024) +)"; + +static void print_version() { + printf("%s version %s\n", g_main_ctx.program_name, hv_compile_version()); +} + +static void print_help() { + printf("Usage: %s [%s]\n", g_main_ctx.program_name, options); + printf("Options:\n%s\n", detail_options); +} + +static kcp_setting_t s_kcp_setting; +static char kcp_host[64] = "0.0.0.0"; +static int kcp_port = 4000; +static hio_t* kcp_io = NULL; + +static char target_host[64] = "127.0.0.1"; +static int target_port = 8080; + +static smux_config_t smux_config; +static smux_session_t smux_session; + +static int verbose = 1; + +/* workflow: + * + * hloop_create_udp_server -> on_recvfrom -> + * + * SYN -> hloop_create_tcp_client -> + * on_connect -> hio_write(kcp_io, SYN) -> + * on_read -> hio_write(kcp_io) -> + * on_close -> hio_write(kcp_io, FIN) -> smux_session_close_stream + * + * PSH -> smux_session_get_stream -> hio_write(stream_io) + * + * FIN -> smux_session_get_stream -> hio_close(stream_io) + * + */ + +// hloop_create_udp_server -> hio_set_kcp -> on_recvfrom -> +// SYN -> hloop_create_tcp_client -> smux_session_open_stream -> +// PSH -> hio_write(io) + +static void on_close(hio_t* io) { + // printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io)); + if (verbose) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printf("disconnected connfd=%d [%s] => [%s]\n", hio_fd(io), + SOCKADDR_STR(hio_localaddr(io), localaddrstr), + SOCKADDR_STR(hio_peeraddr(io), peeraddrstr)); + } + + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(io); + if (smux_stream == NULL) return; + + // FIN + int packlen = smux_stream_output(smux_stream, SMUX_CMD_FIN); + if (packlen > 0) { + // printf("FIN %d\n", packlen); + hio_write(kcp_io, smux_stream->wbuf.base, packlen); + } + + // kill timer + if (smux_stream->timer) { + htimer_del(smux_stream->timer); + smux_stream->timer = NULL; + } + + // free buffer + HV_FREE(smux_stream->rbuf.base); + HV_FREE(smux_stream->wbuf.base); + + smux_session_close_stream(&smux_session, smux_stream->stream_id); + hevent_set_userdata(io, NULL); +} + +static void on_recv(hio_t* io, void* buf, int readbytes) { + // printf("on_recv %.*s \n", readbytes, (char*)buf); + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(io); + if (smux_stream == NULL) return; + + // PSH + smux_frame_t frame; + smux_frame_init(&frame); + frame.head.sid = smux_stream->stream_id; + frame.head.cmd = SMUX_CMD_PSH; + frame.head.length = readbytes; + frame.data = (const char*)buf; + int packlen = smux_frame_pack(&frame, smux_stream->wbuf.base, smux_stream->wbuf.len); + if (packlen > 0) { + // printf("PSH %d\n", packlen); + int nwrite = hio_write(kcp_io, smux_stream->wbuf.base, packlen); + // printf("PSH ret=%d\n", nwrite); + } +} + +static void on_connect(hio_t* io) { + // printf("on_connect fd=%d\n", hio_fd(io)); + if (verbose) { + char localaddrstr[SOCKADDR_STRLEN] = {0}; + char peeraddrstr[SOCKADDR_STRLEN] = {0}; + printf("connected connfd=%d [%s] => [%s]\n", hio_fd(io), + SOCKADDR_STR(hio_localaddr(io), localaddrstr), + SOCKADDR_STR(hio_peeraddr(io), peeraddrstr)); + } + + smux_stream_t* smux_stream = (smux_stream_t*)hevent_userdata(io); + if (smux_stream == NULL) return; + + // SYN + int packlen = smux_stream_output(smux_stream, SMUX_CMD_SYN); + if (packlen > 0) { + // printf("SYN %d\n", packlen); + hio_write(kcp_io, smux_stream->wbuf.base, packlen); + } + + hio_setcb_read(io, on_recv); + hio_read(io); +} + +static void on_kcp_recvfrom(hio_t* io, void* buf, int readbytes) { + // printf("on_kcp_recvfrom %d\n", readbytes); + smux_frame_t frame; + smux_frame_init(&frame); + int packlen = smux_frame_unpack(&frame, buf, readbytes); + assert(packlen == readbytes); + if (packlen < 0 || + frame.head.version > 2 || + frame.head.cmd > SMUX_CMD_UPD) { + fprintf(stderr, "smux_frame_unpack error: %d\n", packlen); + return; + } + // printf("smux sid=%u cmd=%d length=%d\n", frame.head.sid, (int)frame.head.cmd, (int)frame.head.length); + + smux_stream_t* smux_stream = NULL; + if (frame.head.cmd == SMUX_CMD_SYN) { + hio_t* target_io = hio_create_socket(hevent_loop(kcp_io), target_host, target_port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + if (target_io == NULL) { + fprintf(stderr, "create tcp client error!\n"); + return; + } + smux_stream = smux_session_open_stream(&smux_session, frame.head.sid, target_io); + // alloc buffer + smux_stream->rbuf.len = mtu; + smux_stream->wbuf.len = mtu; + HV_ALLOC(smux_stream->rbuf.base, smux_stream->rbuf.len); + HV_ALLOC(smux_stream->wbuf.base, smux_stream->wbuf.len); + hio_set_readbuf(target_io, smux_stream->rbuf.base, smux_config.max_frame_size); + hevent_set_userdata(target_io, smux_stream); + + hio_setcb_connect(target_io, on_connect); + hio_setcb_close(target_io, on_close); + hio_connect(target_io); + } else { + smux_stream = smux_session_get_stream(&smux_session, frame.head.sid); + } + if (smux_stream == NULL) { + if (frame.head.sid != 0 && frame.head.cmd != SMUX_CMD_FIN) { + fprintf(stderr, "recvfrom invalid smux package!\n"); + } + return; + } + + switch (frame.head.cmd) { + case SMUX_CMD_FIN: + hio_close(smux_stream->io); + break; + case SMUX_CMD_PSH: + hio_write(smux_stream->io, frame.data, frame.head.length); + break; + case SMUX_CMD_NOP: + break; + default: + break; + } +} + +int main(int argc, char** argv) { + if (argc < 2) { + print_help(); + exit(0); + } + + // g_main_ctx + main_ctx_init(argc, argv); + //int ret = parse_opt(argc, argv, options); + int ret = parse_opt_long(argc, argv, long_options, ARRAY_SIZE(long_options)); + if (ret != 0) { + print_help(); + exit(ret); + } + + // help + if (get_arg("h")) { + print_help(); + exit(0); + } + + // version + if (get_arg("v")) { + print_version(); + exit(0); + } + +#ifdef OS_UNIX + // daemon + if (get_arg("d")) { + // nochdir, noclose + int ret = daemon(1, 1); + if (ret != 0) { + printf("daemon error: %d\n", ret); + exit(-10); + } + } +#endif + + const char* arg = get_arg("l"); + if (arg) { + localaddr = arg; + } + + arg = get_arg("t"); + if (arg) { + targetaddr = arg; + } + + arg = get_arg("m"); + if (arg) { + mode = arg; + } + + arg = get_arg("mtu"); + if (arg) { + mtu = atoi(arg); + } + + arg = get_arg("sndwnd"); + if (arg) { + sndwnd = atoi(arg); + } + + arg = get_arg("rcvwnd"); + if (arg) { + rcvwnd = atoi(arg); + } + + const char* pos = strchr(localaddr, ':'); + int len = 0; + if (pos) { + len = pos - localaddr; + if (len > 0) { + memcpy(kcp_host, localaddr, len); + kcp_host[len] = '\0'; + } + kcp_port = atoi(pos + 1); + } + + pos = strchr(targetaddr, ':'); + if (pos) { + len = pos - targetaddr; + if (len > 0) { + memcpy(target_host, targetaddr, len); + target_host[len] = '\0'; + } + target_port = atoi(pos + 1); + } + + if (strcmp(mode, "normal") == 0) { + kcp_setting_init_with_normal_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast") == 0) { + kcp_setting_init_with_fast_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast2") == 0) { + kcp_setting_init_with_fast2_mode(&s_kcp_setting); + } else if (strcmp(mode, "fast3") == 0) { + kcp_setting_init_with_fast3_mode(&s_kcp_setting); + } else { + fprintf(stderr, "Unknown mode '%s'\n", mode); + exit(-20); + } + s_kcp_setting.mtu = mtu; + s_kcp_setting.sndwnd = sndwnd; + s_kcp_setting.rcvwnd = rcvwnd; + + printf("smux version: 1\n"); + printf("%s:%d => %s:%d\n", kcp_host, kcp_port, target_host, target_port); + printf("mode: %s\n", mode); + printf("mtu: %d\n", mtu); + printf("sndwnd: %d rcvwnd: %d\n", sndwnd, rcvwnd); + + hloop_t* loop = hloop_new(0); + + kcp_io = hloop_create_udp_server(loop, kcp_host, kcp_port); + if (kcp_io == NULL) { + fprintf(stderr, "create udp server error!\n"); + return -20; + } + hio_set_kcp(kcp_io, &s_kcp_setting); + hio_setcb_read(kcp_io, on_kcp_recvfrom); + hio_read(kcp_io); + + // smux + smux_config.max_frame_size = 1024; + smux_session.next_stream_id = 0; + + hloop_run(loop); + hloop_free(&loop); + return 0; +} diff --git a/examples/kcptun/smux/smux.cpp b/examples/kcptun/smux/smux.cpp new file mode 100644 index 000000000..6c954d68b --- /dev/null +++ b/examples/kcptun/smux/smux.cpp @@ -0,0 +1,79 @@ +#include "smux.h" + +#define SMUX_USE_LITTLE_ENDIAN 1 + +int smux_frame_pack(const smux_frame_t* frame, void* buf, int len) { + if (!frame || !buf || !len) return -1; + const smux_head_t* head = &(frame->head); + unsigned int packlen = smux_package_length(head); + // Check is buffer enough + if (len < packlen) { + return -2; + } + unsigned char* p = (unsigned char*)buf; + *p++ = head->version; + *p++ = head->cmd; +#if SMUX_USE_LITTLE_ENDIAN + *p++ = head->length; + *p++ = (head->length >> 8) & 0xFF; +#else + // hton length + *p++ = (head->length >> 8) & 0xFF; + *p++ = head->length; +#endif + + uint32_t sid = head->sid; +#if SMUX_USE_LITTLE_ENDIAN + *p++ = sid & 0xFF; + *p++ = (sid >> 8) & 0xFF; + *p++ = (sid >> 16) & 0xFF; + *p++ = (sid >> 24) & 0xFF; +#else + // hton sid + *p++ = (sid >> 24) & 0xFF; + *p++ = (sid >> 16) & 0xFF; + *p++ = (sid >> 8) & 0xFF; + *p++ = sid & 0xFF; +#endif + // memcpy data + if (frame->data && head->length) { + memcpy(p, frame->data, frame->head.length); + } + return packlen; +} + +int smux_frame_unpack(smux_frame_t* frame, const void* buf, int len) { + if (!frame || !buf || !len) return -1; + if (len < SMUX_HEAD_LENGTH) return -2; + smux_head_t* head = &(frame->head); + unsigned char* p = (unsigned char*)buf; + head->version = *p++; + head->cmd = *p++; +#if SMUX_USE_LITTLE_ENDIAN + head->length = *p++; + head->length |= ((uint16_t)*p++) << 8; +#else + // ntoh length + head->length = ((uint16_t)*p++) << 8; + head->length |= *p++; +#endif + +#if SMUX_USE_LITTLE_ENDIAN + head->sid = *p++; + head->sid |= ((uint32_t)*p++) << 8; + head->sid |= ((uint32_t)*p++) << 16; + head->sid |= ((uint32_t)*p++) << 24; +#else + // ntoh sid + head->sid = ((uint32_t)*p++) << 24; + head->sid |= ((uint32_t)*p++) << 16; + head->sid |= ((uint32_t)*p++) << 8; + head->sid |= *p++; +#endif + // NOTE: just shadow copy + if (len > SMUX_HEAD_LENGTH) { + frame->data = (const char*)buf + SMUX_HEAD_LENGTH; + } + unsigned int packlen = smux_package_length(head); + return MIN(len, packlen); +} diff --git a/examples/kcptun/smux/smux.h b/examples/kcptun/smux/smux.h new file mode 100644 index 000000000..3cfb8f5a5 --- /dev/null +++ b/examples/kcptun/smux/smux.h @@ -0,0 +1,138 @@ +#ifndef HV_SMUX_H_ +#define HV_SMUX_H_ + +/* + * smux: Simple MUltipleXing used by kcptun + * @see: https://github.com/xtaci/smux + * + */ + +#include + +#include "hplatform.h" +#include "hbase.h" +#include "hbuf.h" +#include "hloop.h" + +typedef enum { + // v1 + SMUX_CMD_SYN = 0, // stream open + SMUX_CMD_FIN = 1, // stream close + SMUX_CMD_PSH = 2, // data push + SMUX_CMD_NOP = 3, // no operation + // v2 + SMUX_CMD_UPD = 4, // update +} smux_cmd_e; + +typedef struct { + uint8_t version; + uint8_t cmd; + uint16_t length; + uint32_t sid; +} smux_head_t; + +#define SMUX_HEAD_LENGTH 8 + +typedef struct { + smux_head_t head; + const char* data; +} smux_frame_t; + +static inline unsigned int smux_package_length(const smux_head_t* head) { + return SMUX_HEAD_LENGTH + head->length; +} + +static inline void smux_head_init(smux_head_t* head) { + head->version = 1; + head->cmd = (uint8_t)SMUX_CMD_PSH; + head->length = 0; + head->sid = 0; +} + +static inline void smux_frame_init(smux_frame_t* frame) { + smux_head_init(&frame->head); + frame->data = NULL; +} + +// @retval >0 package_length, <0 error +int smux_frame_pack(const smux_frame_t* frame, void* buf, int len); +// @retval >0 package_length, <0 error +int smux_frame_unpack(smux_frame_t* frame, const void* buf, int len); + +typedef struct smux_config_s { + int version; + int keepalive_interval; + int keepalive_timeout; + int max_frame_size; + + smux_config_s() { + version = 1; + keepalive_interval = 10000; + keepalive_timeout = 30000; + max_frame_size = 1024; + } +} smux_config_t; + +typedef struct { + uint32_t stream_id; + smux_frame_t frame; + hbuf_t rbuf; + hbuf_t wbuf; + hio_t* io; + htimer_t* timer; +} smux_stream_t; + +// @retval >0 package_length, <0 error, data => wbuf +static inline int smux_stream_output(smux_stream_t* stream, smux_frame_t* frame) { + return smux_frame_pack(frame, stream->wbuf.base, stream->wbuf.len); +} + +static inline int smux_stream_output(smux_stream_t* stream, smux_cmd_e cmd) { + smux_frame_t frame; + smux_frame_init(&frame); + frame.head.sid = stream->stream_id; + frame.head.cmd = (uint8_t)cmd; + return smux_frame_pack(&frame, stream->wbuf.base, stream->wbuf.len); +} + +// @retval >0 package_length, <0 error, data => frame +static inline int smux_stream_input(smux_stream_t* stream, const void* buf, int len) { + return smux_frame_unpack(&stream->frame, buf, len); +} + +typedef struct { + uint32_t next_stream_id; + // stream_id => smux_stream_t + std::map streams; +} smux_session_t; + +static inline smux_stream_t* smux_session_open_stream(smux_session_t* session, uint32_t stream_id = 0, hio_t* io = NULL) { + smux_stream_t* stream = NULL; + HV_ALLOC_SIZEOF(stream); + if (stream_id == 0) { + session->next_stream_id += 2; + stream_id = session->next_stream_id; + } + stream->stream_id = stream_id; + session->streams[stream_id] = stream; + stream->io = io; + return stream; +} + +static inline smux_stream_t* smux_session_get_stream(smux_session_t* session, uint32_t stream_id) { + auto iter = session->streams.find(stream_id); + if (iter != session->streams.end()) { + return iter->second; + } + return NULL; +} + +static inline void smux_session_close_stream(smux_session_t* session, uint32_t stream_id) { + auto iter = session->streams.find(stream_id); + if (iter != session->streams.end()) { + HV_FREE(iter->second); + session->streams.erase(iter); + } +} + +#endif // HV_SMUX_H_ From 3bac06a0878789e351ad49d222126afb6afd6f0c Mon Sep 17 00:00:00 2001 From: ithewei Date: Thu, 18 Apr 2024 22:37:21 +0800 Subject: [PATCH 2/2] Add README for kcptun --- examples/kcptun/README.md | 68 +++++++++++++++++++++++++++++++++++++ examples/kcptun/kcptun.png | Bin 0 -> 33462 bytes 2 files changed, 68 insertions(+) create mode 100644 examples/kcptun/README.md create mode 100644 examples/kcptun/kcptun.png diff --git a/examples/kcptun/README.md b/examples/kcptun/README.md new file mode 100644 index 000000000..502ce43da --- /dev/null +++ b/examples/kcptun/README.md @@ -0,0 +1,68 @@ +# Intro + +kcptun + +> *Disclaimer: The picture comes from [github.com/xtaci/kcptun](https://github.com/xtaci/kcptun). Thanks so much.* + +# Build + +```shell +./configure --with-kcp +make clean +make examples +make kcptun +``` + +# Usage + +```shell +$ bin/kcptun_server -h + +Usage: kcptun_server [hvdl:t:m:] +Options: + + -h|--help Print this information + -v|--version Print version + -d|--daemon Daemonize + -l|--listen value kcp server listen address (default: ":4000") + -t|--target value target server address (default: "127.0.0.1:8080") + -m|--mode value profiles: fast3, fast2, fast, normal (default: "fast") + --mtu value set maximum transmission unit for UDP packets (default: 1350) + --sndwnd value set send window size(num of packets) (default: 1024) + --rcvwnd value set receive window size(num of packets) (default: 1024) +``` + +```shell +$ bin/kcptun_client -h + +Usage: kcptun_client [hvdl:r:m:] +Options: + + -h|--help Print this information + -v|--version Print version + -d|--daemon Daemonize + -l|--localaddr value local listen address (default: ":8388") + -r|--remoteaddr value kcp server address (default: "127.0.0.1:4000") + -m|--mode value profiles: fast3, fast2, fast, normal (default: "fast") + --mtu value set maximum transmission unit for UDP packets (default: 1350) + --sndwnd value set send window size(num of packets) (default: 128) + --rcvwnd value set receive window size(num of packets) (default: 512) +``` + +# Test +`tcp_client -> kcptun_client -> kcptun_server -> tcp_server` +```shell +tcp_server: bin/tcp_echo_server 1234 +kcptun_server: bin/kcptun_server -l :4000 -t 127.0.0.1:1234 --mode fast3 +kcptun_client: bin/kcptun_client -l :8388 -r 127.0.0.1:4000 --mode fast3 +tcp_client: bin/nc 127.0.0.1 8388 + > hello + < hello +``` + +This kcptun examples does not implement encryption, compression, and fec.
+if you want to use [github.com/xtaci/kcptun](https://github.com/xtaci/kcptun), please add `--crypt null --nocomp --ds 0 --ps 0`.
+For example: +```shell +golang_kcptun_server -l :4000 -t 127.0.0.1:1234 --mode fast3 --crypt null --nocomp --ds 0 --ps 0 +``` diff --git a/examples/kcptun/kcptun.png b/examples/kcptun/kcptun.png new file mode 100644 index 0000000000000000000000000000000000000000..833f5c5f6a20cf71e63067a23ea04da031b92dd5 GIT binary patch literal 33462 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%0Ar>}o8qFDl!RDz_k%Kr6@76rMMz{I2-} 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 literal 0 HcmV?d00001