diff --git a/Makefile b/Makefile index 96e15a8cd..a52ca3b75 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ examples: hmain_test htimer_test hloop_test \ tcp_proxy_server \ udp_echo_server \ udp_proxy_server \ + socks5_proxy_server \ multi-acceptor-processes \ multi-acceptor-threads \ one-acceptor-multi-workers \ @@ -103,6 +104,9 @@ udp_echo_server: prepare udp_proxy_server: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS)" SRCS="examples/udp_proxy_server.c" +socks5_proxy_server: prepare + $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS)" SRCS="examples/socks5_proxy_server.c" + multi-acceptor-processes: prepare $(MAKEF) TARGET=$@ SRCDIRS="$(CORE_SRCDIRS)" SRCS="examples/multi-thread/multi-acceptor-processes.c" diff --git a/README-CN.md b/README-CN.md index e2c40937a..c610c3e19 100644 --- a/README-CN.md +++ b/README-CN.md @@ -372,6 +372,7 @@ int main() { - TCP代理服务: [examples/tcp_proxy_server.c](examples/tcp_proxy_server.c) - UDP回显服务: [examples/udp_echo_server.c](examples/udp_echo_server.c) - UDP代理服务: [examples/udp_proxy_server.c](examples/udp_proxy_server.c) +- SOCKS5代理服务: [examples/socks5_proxy_server.c](examples/socks5_proxy_server.c) - TinyHttpd示例:[examples/tinyhttpd.c](examples/tinyhttpd.c) - TinyProxyd示例:[examples/tinyproxyd.c](examples/tinyproxyd.c) - jsonRPC示例: [examples/jsonrpc](examples/jsonrpc) diff --git a/README.md b/README.md index 5b84ccbfb..adf277b69 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,7 @@ int main() { - [examples/tcp_proxy_server.c](examples/tcp_proxy_server.c) - [examples/udp_echo_server.c](examples/udp_echo_server.c) - [examples/udp_proxy_server.c](examples/udp_proxy_server.c) +- [examples/socks5_proxy_server.c](examples/socks5_proxy_server.c) - [examples/tinyhttpd.c](examples/tinyhttpd.c) - [examples/tinyproxyd.c](examples/tinyproxyd.c) - [examples/jsonrpc](examples/jsonrpc) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 57ed88439..9ca737ee0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,6 +9,7 @@ list(APPEND EXAMPLES tcp_proxy_server udp_echo_server udp_proxy_server + socks5_proxy_server jsonrpc_client jsonrpc_server ) @@ -45,6 +46,9 @@ target_link_libraries(udp_echo_server ${HV_LIBRARIES}) add_executable(udp_proxy_server udp_proxy_server.c) target_link_libraries(udp_proxy_server ${HV_LIBRARIES}) +add_executable(socks5_proxy_server socks5_proxy_server.c) +target_link_libraries(socks5_proxy_server ${HV_LIBRARIES}) + add_executable(jsonrpc_client jsonrpc/jsonrpc_client.c jsonrpc/cJSON.c) target_link_libraries(jsonrpc_client ${HV_LIBRARIES}) diff --git a/examples/README.md b/examples/README.md index 2b2012d6c..c37dc1432 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,6 +16,7 @@ ├── http_client_test.c HTTP客户端测试代码 ├── http_server_test.c HTTP服务端测试代码 ├── nc.c 网络连接工具 +├── socks5_proxy_server.c SOCKS5代理服务 ├── tcp_chat_server.c TCP聊天服务 ├── tcp_echo_server.c TCP回显服务 ├── tcp_proxy_server.c TCP代理服务 diff --git a/examples/socks5_proxy_server.c b/examples/socks5_proxy_server.c new file mode 100644 index 000000000..a7e437550 --- /dev/null +++ b/examples/socks5_proxy_server.c @@ -0,0 +1,404 @@ +/* + * socks5 proxy server + * + * @build: make examples + * + * @proxy_server bin/socks5_proxy_server 1080 + * bin/socks5_proxy_server 1080 username password + * + * @proxy_client curl -v http://www.example.com/ --proxy socks5://127.0.0.1:1080 + * curl -v http://www.example.com/ --proxy socks5://username:password@127.0.0.1:1080 + * + */ + +#include "hv.h" +#include "hloop.h" + +static char proxy_host[64] = "0.0.0.0"; +static int proxy_port = 1080; + +static const char* auth_username = NULL; +static const char* auth_password = NULL; + +#define SOCKS5_VERSION ((uint8_t)5) + +#define SOCKS5_AUTH_VERSION ((uint8_t)1) +#define SOCKS5_AUTH_SUCCESS ((uint8_t)0) +#define SOCKS5_AUTH_FAILURE ((uint8_t)1) + +typedef enum { + NoAuth = 0, + GssApiAuth = 1, + UserPassAuth = 2, +} socks5_auth_method; + +typedef enum { + ConnectCommand = 1, + BindCommand = 2, + AssociateCommand= 3, +} socks5_command; + +typedef enum { + IPv4Addr = 1, + FqdnAddr = 3, + IPv6Addr = 4, +} socks5_addr_type; + +typedef enum { + SuccessReply = 0, + ServerFailure = 1, + RuleFailure = 2, + NetworkUnreachable = 3, + HostUnreachable = 4, + ConnectRefused = 5, + TtlExpired = 6, + CommandNotSupported = 7, + AddrTypeNotSupported= 8, +} socks5_reply_code; + +typedef enum { + s_begin, + s_auth_methods_count, + s_auth_methods, + s_auth_username_len, + s_auth_username, + s_auth_password_len, + s_auth_password, + s_request, + s_dst_addr_type, + s_dst_addr_len, + s_dst_addr, + s_dst_port, + s_upstream, + s_end, +} socks5_state_e; + +typedef struct { + hio_t* io; + socks5_state_e state; + socks5_addr_type addr_type; + sockaddr_u addr; +} socks5_conn_t; + +/* + * workflow: + * hloop_new -> hloop_create_tcp_server -> hloop_run + * on_accept -> HV_ALLOC(socks5_conn_t) -> hio_readbytes(s_auth_methods_count:2) -> + * on_recv -> hio_readbytes(s_auth_methods) -> + * on_recv -> hio_write(auth_method) -> hio_readbytes(s_auth_username_len:2) -> + * on_recv -> hio_readbytes(s_auth_username) -> + * on_recv -> hio_readbytes(s_auth_password_len:1) -> + * on_recv -> hio_readbytes(s_auth_password) -> hio_write(auth_result) -> + * on_recv -> hio_readbytes(s_request:3) -> + * on_recv -> hio_readbytes(addr_type:1) -> + * on_recv -> hio_readbytes(addr_len:1) -> + * on_recv -> hio_readbytes(addr) -> + * on_recv -> hio_readbytes(port:2) -> + * on_recv -> hio_setup_tcp_upstream(io, addr, port) -> + * on_close -> hio_close_upstream -> HV_FREE(socks5_conn_t) + * + */ + +static void on_upstream_connect(hio_t* upstream_io) { + // printf("on_upstream_connect connfd=%d\n", hio_fd(upstream_io)); + socks5_conn_t* conn = (socks5_conn_t*)hevent_userdata(upstream_io); + sockaddr_u* localaddr = (sockaddr_u*)hio_localaddr(upstream_io); + uint8_t resp[32] = { SOCKS5_VERSION, SuccessReply, 0, IPv4Addr, 127,0,0,1, 0,80, 0 }; + int resp_len = 3; + if (localaddr->sa.sa_family == AF_INET) { + resp[resp_len++] = IPv4Addr; + memcpy(resp + resp_len, &localaddr->sin.sin_addr, 4); resp_len += 4; + memcpy(resp + resp_len, &localaddr->sin.sin_port, 2); resp_len += 2; + } else if (localaddr->sa.sa_family == AF_INET6) { + resp[resp_len++] = IPv6Addr; + memcpy(resp + resp_len, &localaddr->sin6.sin6_addr, 16); resp_len += 16; + memcpy(resp + resp_len, &localaddr->sin6.sin6_port, 2); resp_len += 2; + } + hio_write(conn->io, resp, resp_len); + hio_setcb_read(upstream_io, hio_write_upstream); + hio_setcb_read(conn->io, hio_write_upstream); + hio_read(conn->io); + hio_read(upstream_io); +} + +static void on_close(hio_t* io) { + // printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io)); + socks5_conn_t* conn = (socks5_conn_t*)hevent_userdata(io); + if (conn) { + hevent_set_userdata(io, NULL); + HV_FREE(conn); + } + hio_close_upstream(io); +} + +static void on_recv(hio_t* io, void* buf, int readbytes) { + socks5_conn_t* conn = (socks5_conn_t*)hevent_userdata(io); + uint8_t* bytes = (uint8_t*)buf; + switch(conn->state) { + case s_begin: + // printf("s_begin\n"); + conn->state = s_auth_methods_count; + case s_auth_methods_count: + // printf("s_auth_methods_count\n"); + { + assert(readbytes == 2); + uint8_t version = bytes[0]; + uint8_t methods_count = bytes[1]; + if (version != SOCKS5_VERSION || methods_count == 0) { + fprintf(stderr, "Unsupprted socks version: %d\n", (int)version); + hio_close(io); + return; + } + conn->state = s_auth_methods; + hio_readbytes(io, methods_count); + } + break; + case s_auth_methods: + // printf("s_auth_methods\n"); + { + // TODO: check auth methos + uint8_t auth_method = NoAuth; + if (auth_username && auth_password) { + auth_method = UserPassAuth; + } else { + // TODO: Implement more auth methods + } + // send auth mothod + uint8_t resp[2] = { SOCKS5_VERSION, NoAuth }; + resp[1] = auth_method; + hio_write(io, resp, 2); + if (auth_method == NoAuth) { + conn->state = s_request; + hio_readbytes(io, 3); + } else if (auth_method == UserPassAuth) { + conn->state = s_auth_username_len; + hio_readbytes(io, 2); + } + } + break; + case s_auth_username_len: + // printf("s_auth_username_len\n"); + { + assert(readbytes == 2); + uint8_t auth_version = bytes[0]; + uint8_t username_len = bytes[1]; + if (auth_version != SOCKS5_AUTH_VERSION || username_len == 0) { + fprintf(stderr, "Unsupported auth version: %d\n", (int)auth_version); + hio_close(io); + return; + } + conn->state = s_auth_username; + hio_readbytes(io, username_len); + } + break; + case s_auth_username: + // printf("s_auth_username\n"); + { + char* username = (char*)bytes; + printf("username=%.*s\n", readbytes, username); + if (readbytes != strlen(auth_username) || + strncmp(username, auth_username, readbytes) != 0) { + fprintf(stderr, "User authentication failed!\n"); + uint8_t resp[2] = { SOCKS5_AUTH_VERSION, SOCKS5_AUTH_FAILURE }; + hio_write(io, resp, 2); + hio_close(io); + return; + } + conn->state = s_auth_password_len; + hio_readbytes(io, 1); + } + break; + case s_auth_password_len: + // printf("s_auth_password_len\n"); + { + assert(readbytes == 1); + uint8_t password_len = bytes[0]; + if (password_len == 0) { + fprintf(stderr, "Miss password\n"); + uint8_t resp[2] = { SOCKS5_AUTH_VERSION, SOCKS5_AUTH_FAILURE }; + hio_write(io, resp, 2); + hio_close(io); + return; + } + conn->state = s_auth_password; + hio_readbytes(io, password_len); + } + break; + case s_auth_password: + // printf("s_auth_password\n"); + { + char* password = (char*)bytes; + printf("password=%.*s\n", readbytes, password); + uint8_t resp[2] = { SOCKS5_AUTH_VERSION, SOCKS5_AUTH_SUCCESS }; + if (readbytes != strlen(auth_password) || + strncmp(password, auth_password, readbytes) != 0) { + fprintf(stderr, "User authentication failed!\n"); + resp[1] = SOCKS5_AUTH_FAILURE; + hio_write(io, resp, 2); + hio_close(io); + return; + } + hio_write(io, resp, 2); + conn->state = s_request; + hio_readbytes(io, 3); + } + break; + case s_request: + // printf("s_request\n"); + { + assert(readbytes == 3); + uint8_t version = bytes[0]; + uint8_t cmd = bytes[1]; + if (version != SOCKS5_VERSION || cmd != ConnectCommand) { + // TODO: Implement other commands + fprintf(stderr, "Unsupported command: %d\n", (int)cmd); + hio_close(io); + return; + } + conn->state = s_dst_addr_type; + hio_readbytes(io, 1); + } + break; + case s_dst_addr_type: + // printf("s_dst_addr_type\n"); + { + assert(readbytes == 1); + conn->addr_type = (socks5_addr_type)bytes[0]; + if (conn->addr_type == IPv4Addr) { + conn->state = s_dst_addr; + hio_readbytes(io, 4); + } else if (conn->addr_type == FqdnAddr) { + conn->state = s_dst_addr_len; + hio_readbytes(io, 1); + } else if (conn->addr_type == IPv6Addr) { + conn->state = s_dst_addr; + hio_readbytes(io, 16); + } else { + fprintf(stderr, "Unsupported addr type: %d\n", (int)conn->addr_type); + hio_close(io); + return; + } + } + break; + case s_dst_addr_len: + // printf("s_dst_addr_len\n"); + { + uint8_t addr_len = bytes[0]; + if (addr_len == 0) { + fprintf(stderr, "Miss domain!\n"); + hio_close(io); + return; + } + conn->state = s_dst_addr; + hio_readbytes(io, addr_len); + } + break; + case s_dst_addr: + // printf("s_dst_addr\n"); + { + if (conn->addr_type == IPv4Addr) { + assert(readbytes == 4); + conn->addr.sa.sa_family = AF_INET; + memcpy(&conn->addr.sin.sin_addr, bytes, 4); + } else if (conn->addr_type == IPv6Addr) { + assert(readbytes == 16); + conn->addr.sa.sa_family = AF_INET6; + memcpy(&conn->addr.sin6.sin6_addr, bytes, 16); + } else { + char* host = (char*)bytes; + host[readbytes] = '\0'; + // TODO: async DNS + if (ResolveAddr(host, &conn->addr) != 0) { + fprintf(stderr, "Resovle %s failed!\n", host); + hio_close(io); + return; + } + } + conn->state = s_dst_port; + hio_readbytes(io, 2); + } + break; + case s_dst_port: + // printf("s_dst_port\n"); + { + assert(readbytes == 2); + uint16_t port = ((uint16_t)bytes[0]) << 8 | bytes[1]; + // printf("port=%d\n", port); + sockaddr_set_port(&conn->addr, port); + hloop_t* loop = hevent_loop(io); + // hio_t* upstream_io = hio_setup_tcp_upstream(io, conn->host, conn->port, 0); + // hio_t* upstream_io = hio_create_socket(loop, conn->host, conn->port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + int sockfd = socket(conn->addr.sa.sa_family, SOCK_STREAM, 0); + if (sockfd < 0) { + perror("socket"); + hio_close(io); + return; + } + hio_t* upstream_io = hio_get(loop, sockfd); + assert(upstream_io != NULL); + hio_set_peeraddr(upstream_io, &conn->addr.sa, sockaddr_len(&conn->addr)); + + hevent_set_userdata(upstream_io, conn); + // io <=> upstream_io + hio_setup_upstream(io, upstream_io); + hio_setcb_connect(upstream_io, on_upstream_connect); + hio_setcb_close(upstream_io, hio_close_upstream); + conn->state = s_upstream; + // printf("connect to "); + // SOCKADDR_PRINT(hio_peeraddr(upstream_io)); + hio_connect(upstream_io); + } + break; + case s_upstream: + hio_write_upstream(io, buf, readbytes); + break; + case s_end: + break; + default: + break; + } +} + +static void on_accept(hio_t* io) { + /* + printf("on_accept connfd=%d\n", hio_fd(io)); + 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_read(io, on_recv); + hio_setcb_close(io, on_close); + + socks5_conn_t* conn = NULL; + HV_ALLOC_SIZEOF(conn); + conn->io = io; + hevent_set_userdata(io, conn); + // start read + conn->state = s_auth_methods_count; + hio_readbytes(io, 2); +} + +int main(int argc, char** argv) { + if (argc < 2) { + printf("Usage: %s proxy_port [username] [password]\n", argv[0]); + return -10; + } + proxy_port = atoi(argv[1]); + if (argc > 3) { + auth_username = argv[2]; + auth_password = argv[3]; + } + + hloop_t* loop = hloop_new(0); + hio_t* listenio = hloop_create_tcp_server(loop, proxy_host, proxy_port, on_accept); + if (listenio == NULL) { + return -20; + } + printf("socks5 proxy server listening on %s:%d, listenfd=%d\n", proxy_host, proxy_port, hio_fd(listenio)); + hloop_run(loop); + hloop_free(&loop); + return 0; +}