瀏覽代碼

Add examples socks5_proxy_server

ithewei 4 年之前
父節點
當前提交
a0ffa88151
共有 6 個文件被更改,包括 415 次插入0 次删除
  1. 4 0
      Makefile
  2. 1 0
      README-CN.md
  3. 1 0
      README.md
  4. 4 0
      examples/CMakeLists.txt
  5. 1 0
      examples/README.md
  6. 404 0
      examples/socks5_proxy_server.c

+ 4 - 0
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"
 

+ 1 - 0
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)

+ 1 - 0
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)

+ 4 - 0
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})
 

+ 1 - 0
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代理服务

+ 404 - 0
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;
+}