首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Socks4客户机/服务器实现

Socks4客户机/服务器实现
EN

Code Review用户
提问于 2023-04-27 09:18:04
回答 3查看 135关注 0票数 5

我实现了一个基本的Socks4客户机和服务器,它现在只能处理CONNECT请求,也没有身份验证协议支持。

我已经用一个简单的ncat回显服务器对它进行了测试,但我想回顾一下我的:

  • 项目结构
  • 文件化/代码分裂
  • 结构命名/设计
  • 错误处理
  • 当然还有密码。
  • 处理多个连接(每个会话的线程?线程池?)

我试图创建一个灵活的socks_configsocks结构,但我不确定是否成功。我真的不想让socks4_clientsocks4_server为用户实现一个简单而小的界面,如果它是错误的“对象类型”,函数就会返回。

我读过关于使用轮询和选择与许多连接,这是不推荐的,所以我坚持线程的方式,但线程创建是昂贵的。很高兴听到你认为对的事。

我也不确定我的民意测验是愚蠢的还是正确的,或者可能更好。

正如您所注意到的,accept函数很长并且不分裂。那是因为我不知道该怎么分它。但也许你有一些提示给我。

在编写这篇文章时,我意识到accept函数是阻塞的,直到客户机发送一个连接请求。那么,我应该在accept()调用之后将所有过程添加到线程函数中吗?那么我的接受函数没有必要吗?

我有一个回购,可在:https://github.com/mortytheshorty/socks

这是我的项目结构:

代码语言:javascript
复制
├── CMakeLists.txt
├── include
│   └── socks.h
├── socks4.rfc
└── src
    ├── include
    │   ├── socket_config.h
    │   ├── socks4.h
    │   └── socks_internal.h
    ├── readFromSocket.c
    ├── sendToSocket.c
    ├── socket_configuration.c
    ├── socks_accept.c
    ├── socks_close.c
    ├── socks_connect.c
    ├── socks_init.c
    ├── socks_listen.c
    ├── socks_log.c
    ├── test
    │   ├── client.c
    │   └── server.c
    └── thread.c

在这里,源代码:

socks.h

代码语言:javascript
复制
//
// Created by flo on 4/18/23.
//

#ifndef SOCKS_SOCKS_H
#define SOCKS_SOCKS_H

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdarg.h>


#define SOCKS4_VERSION 0x04

/* Socks types*/
enum SOCKS_TYPE {SOCKS_SERVER, SOCKS_CLIENT};


typedef struct socks_config {
    int type; // SOCKS_SERVER or SOCKS_CLIENT
    int maxconn; // maximum connections possible if SOCKS_SERVER else irgnored
    char *ip; // SOCKS_SERVER == interface the to listen on, SOCKS_CLIENT == server address to connect to
    uint16_t port; // SOCKS_SERVER == port to listen on, SOCKS_CLIENT == socks server port to connect to
    char *filename; // log file name
    uint8_t version; // socks version
} socks_config;

typedef struct socks {
    socks_config *config; // pointer to socks config
    int sock; // network socket
    char *ip; // destination ip address to connect to via socks proxy or unused as server
    uint16_t port; // destination port to connect to via socks proxy or unused as server
    FILE *log; // log file handle
} socks;

typedef struct socks_connection {
    int client_sock;
    int target_sock;
} socks_connection;

/**
 * @brief Initializes the socks structure for client and server aswell
 * 
 * @param socks socks strucuture
 * @param config socks configruation
 * @retval 0 on success
 * @retval n on failure
 */
int socks_init(socks *socks, socks_config *config);

/**
 * @brief Connectes a socks client with a remote peer via the proxy
 * 
 * @param client socks client structure
 * @param ip destination ip
 * @param port destination port
 * @retval 0 on success
 * @retval n on failure
 */
int socks_connect(socks *client, char *ip, int port);

/**
 * @brief Starts listening on configured server
 * 
 * @param proxy socks server structure
 * @retval 0 on success
 * @retval n on failure
 */
int socks_listen(socks *proxy);


/**
 * @brief Sends data of specified size to socks socket
 * 
 * @param socks socks socks structure
 * @param data data
 * @param len size of data
 * @retval n on success
 * @retval -1 on failure
 */
ssize_t socks_send(socks *socks, void *data, size_t len);

/**
 * @brief Receives data of specified size to socks socket
 * 
 * @param socks socks socks structure
 * @param data data
 * @param len size of data
 * @retval n on success
 * @retval -1 on failure
 */
ssize_t socks_recv(socks *socks, void *data, size_t len);

/**
 * @brief Destroys socks structure
 * 
 * @param socks socks strucutre
 */
void socks_destroy(socks *socks);



#endif //SOCKS_SOCKS_H

socket_config.h

代码语言:javascript
复制
#ifndef SOCKS_CONFIG_H
#define SOCKS_CONFIG_H

#include <inttypes.h>

/* not sure if I want to use it */
void socket_enable_keepalive(int sock);

/* gets ipv4 address and port form socket file descriptor */
/* NOTE: buffer and bufsz must be at least INET6_ADDRSTRLEN or bigger */
int socket_peer_addrinfo(int sock, char *buffer, size_t bufsz, uint16_t *port);

#endif

socks4.h

代码语言:javascript
复制
//
// Created by flo on 4/18/23.
//

#ifndef SOCKS_SOCKS4_H
#define SOCKS_SOCKS4_H

#include <inttypes.h>

#define PACKED __attribute__((__packed__))

#define SOCKS4_VERSION 0x04
#define SOCKS4_CMD_CONNECT 0x01
#define SOCKS4_CMD_BIND 0x02

struct PACKED socks4_request {
    uint8_t version;
    uint8_t cmd;
    uint16_t port; // network byte order
    uint32_t ip; // network byte order
    uint8_t userid[1];
};
typedef struct socks4_request socks4_request;


#define SOCKS4_REPLY_GRANTED 0x5A
#define SOCKS4_REPLY_FAILED 0x5B
// #define SOCKS4_REPLY_IDENT_CONNECT_FAILED 0x5C
// #define SOCKS4_REPLY_IDENT_AUTH_FAILED 0x5D

struct PACKED socks4_reply {
    uint8_t version;
    uint8_t code;
    uint16_t port;
    uint32_t ip;
};
typedef struct socks4_reply socks4_reply;


enum SOCKS4_ERRORS {
    SOCKS_OK,
    SOCKS_SEND_ERR,
    SOCKS_RECV_ERR,
    SOCKS_REPLY_INVAL,
    SOCKS_REQUEST_DENY,
    SOCKS_SOCKCREAT_ERR,
    SOCKS_LOGCREAT_ERR,
    SOCKS_IP_INVAL,
    SOCKS_SOCKREUSE_ERR,
    SOCKS_SERVER_BIND_ERR,
    SOCKS_CLIENT_CONNECT_ERR,
    SOCKS_SERVER_ACCEPT_ERR,
    SOCKS_SERVER_CONNECT_ERR, // server can not connect to destination
    SOCKS_LISTEN_ERR,
};

#endif //SOCKS_SOCKS4_H

socks_internal.h

代码语言:javascript
复制
#ifndef SOCKS_INTERNAL_H
#define SOCKS_INTERNAL_H

#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <pthread.h>

#include "../../include/socks.h"
#include "socket_config.h"
#include "socks4.h"


ssize_t readFromSocket(int sock, void *buf, size_t size);
ssize_t sendToSocket(int sock, void *buf, size_t size);

void *socks_connection_thread(void *pipefdsp);
int socks_accept(socks *proxy, socks_connection *conn);
int socks_log(socks *socks, const char *function, const char *fmt, ...);
#endif

readFromSocket.c

代码语言:javascript
复制
#include "include/socks_internal.h"

ssize_t readFromSocket(int sock, void *buf, size_t size)
{
    char *pbuf = (char*) buf;
    ssize_t num_bytes, total = 0;

    while (total < size) {
        num_bytes = recv(sock, &pbuf[total], size-total, 0);
        if (num_bytes <= 0) {
            if ((num_bytes < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
                break;
            }
            return -1;
        }
        total += num_bytes;
    }

    return total;
}

sendToSocket.c

代码语言:javascript
复制
#include "include/socks_internal.h"

ssize_t sendToSocket(int sock, void *buf, size_t size)
{
    char *pbuf = (char*) buf;
    ssize_t num_bytes, total = 0;

    while (total < size) {
        num_bytes = send(sock, &pbuf[total], size-total, 0);
        if (num_bytes < 0) {
            if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
                break;
            }
            return -1;
        }
        total += num_bytes;
    }

    return total;
}

socket_config.c

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/signal.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define check(expr) if (!(expr)) { perror(#expr); kill(0, SIGTERM); }

void socket_enable_keepalive(int sock) {
    int yes = 1;
    check(setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1);

    int idle = 1;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int)) != -1);

    int interval = 1;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(int)) != -1);

    int maxpkt = 10;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(int)) != -1);
}


void *get_in_addr(const struct sockaddr_storage *storage) {

    if( storage->ss_family == AF_INET) // IPv4 address
        return &(((struct sockaddr_in*)storage)->sin_addr);
    // else IPv6 address
    return &(((struct sockaddr_in6*)storage)->sin6_addr);
}

int socket_peer_addrinfo(int sock, char *buffer, size_t bufsz, uint16_t *port)
{
    if(bufsz != INET6_ADDRSTRLEN) {
        return -1;
    }

    struct sockaddr_storage inaddr;
    socklen_t inaddr_len = sizeof(inaddr);
    
    getpeername(sock, (struct sockaddr*) &inaddr, &inaddr_len);

    if(inet_ntop(inaddr.ss_family, get_in_addr(&inaddr), buffer, inaddr_len) == NULL) {
        return -1;
    }
    
    if(port) {
        *port = ntohs(((struct sockaddr_in*)&inaddr)->sin_port);
    }

    return 0;
}

int socket_local_addrinfo(int sock, char *buffer, size_t bufsz, uint16_t *port)
{
    if(bufsz < INET6_ADDRSTRLEN) {
        return -1;
    }

    struct sockaddr_storage inaddr;
    socklen_t inaddr_len = sizeof(inaddr);
    
    getsockname(sock, (struct sockaddr*) &inaddr, &inaddr_len);

    if(inet_ntop(inaddr.ss_family, get_in_addr(&inaddr), buffer, inaddr_len) == NULL) {
        return -1;
    }
    

    if(port) {
        *port = ntohs(((struct sockaddr_in*)&inaddr)->sin_port);
    }

    return 0;
}

socks_accept.c

代码语言:javascript
复制
#include "include/socks_internal.h"


int target_connect(socks *proxy, socks4_request *req, int client_sock, int *target_sock)
{
    char ip[INET6_ADDRSTRLEN];
    uint16_t port;
    socks4_reply reply;
    struct sockaddr_in inaddr;
    memset(&inaddr, 0, sizeof(inaddr));

    inaddr.sin_family = AF_INET;
    inaddr.sin_addr.s_addr = req->ip;
    inaddr.sin_port = req->port;

    *target_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(target_sock < 0) {
        socks_log(proxy, __FUNCTION__, "failed to create remote peer socket.");
        return SOCKS_SOCKCREAT_ERR; // SOCKS_SERVER_TARGET_SOCKET_CREATE_ERROR
    }

    socket_peer_addrinfo(client_sock, ip, sizeof(ip), &port);
    // we try to connect to the remote peer for the client
    if(connect(*target_sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) {
        reply.version = SOCKS4_VERSION;
        reply.code = SOCKS4_REPLY_FAILED;
        reply.ip = inaddr.sin_addr.s_addr;
        reply.port = inaddr.sin_port;
        
        if(sendToSocket(client_sock, &reply, sizeof(reply)) != sizeof(reply)) {
            socks_log(proxy, __FUNCTION__, "failed to send reply about failed connect to %s:%d", ip, port);
            return SOCKS_SEND_ERR;
        }

        socks_log(proxy, __FUNCTION__, "failed to connect to remote peer '%s:%d'", ip, port);

        return SOCKS_SERVER_CONNECT_ERR; // SOCKS_SERVER_TARGET_CONNECT_ERROR
    }

    socks_log(proxy, __FUNCTION__, "CONNECT from %s:%d to %s:%d", ip, port, inet_ntoa(*(struct in_addr*) &req->ip), ntohs(req->port));
    return SOCKS_OK;
}

int socks_accept(socks *proxy, socks_connection *conn)
{
    if(proxy->config->type != SOCKS_SERVER) {
        socks_log(proxy, __FUNCTION__, "can not listen to socks handle. socks handle is a client");
    }
    
    socks4_request request;
    socks4_reply reply;

    char ip[INET6_ADDRSTRLEN];
    uint16_t port;
    int target_sock = -1;
    int client_sock = -1;
    int rc = 0;
    
    client_sock = accept(proxy->sock, NULL, NULL);
    if(client_sock < 0) {
        socks_log(proxy, __FUNCTION__, "socks_accept: failed to accept connection from %s", "unknown");
        return SOCKS_SERVER_ACCEPT_ERR; // SOCKS_SERVER_ACCEPT_ERROR see: errno
    }


    socket_peer_addrinfo(client_sock, ip, sizeof(ip), &port);
    socks_log(proxy, __FUNCTION__, "connection from %s:%d", ip, port);

    if(readFromSocket(client_sock, &request, sizeof(request)) != sizeof(request)) {
        socks_log(proxy, __FUNCTION__, "failed to receive request from '%s:%d'", ip, port);
        close(client_sock);
        return SOCKS_RECV_ERR; // SOCKS_SERVER_RECV_ERROR 
    }
    
    if(request.userid != 0) {
        // identifaction protocol code
    }


    rc = target_connect(proxy, &request, client_sock, &target_sock);
    if( rc ) {
        return SOCKS_SERVER_CONNECT_ERR;
    }


    // build the reply
    reply.version = SOCKS4_VERSION;
    reply.code = SOCKS4_REPLY_GRANTED;
    reply.port = request.port;
    reply.ip = request.ip;

    // send it back to the client
    if(sendToSocket(client_sock, &reply, sizeof(reply)) != sizeof(reply)) {
        socks_log(proxy, __FUNCTION__, "failed to send reply about successful connect to '%s:%d'", "unknown", 0);
        close(client_sock);
        return SOCKS_SEND_ERR; // SOCKS_SERVER_SEND_ERROR
    }

    
    conn->client_sock = client_sock;
    conn->target_sock = target_sock;

    return 0;
}

socks_close.c

代码语言:javascript
复制
#include "include/socks_internal.h"

void socks_destroy(socks *socks_handle)
{
    close(socks_handle->sock);
    socks_log(socks_handle, __FUNCTION__, "closing connection");
    if(socks_handle->log) {
        fclose(socks_handle->log);
    }
}

socks_connect.c

代码语言:javascript
复制
#include "include/socks_internal.h"

int socks_connect(socks *client, char *ip, int port) {
    
    client->ip = ip;
    client->port = port;

    ssize_t ret = 0;
    socks4_request request;

    request.version = SOCKS4_VERSION;
    request.cmd = SOCKS4_CMD_CONNECT;
    request.port = htons((short) port);
    request.ip = inet_addr(ip);
    request.userid[0] = 0x00;

    if((ret = sendToSocket(client->sock, &request, sizeof(request))) != sizeof(request)) {
        socks_log(client, __FUNCTION__, "failed to send CONNECT request");
        return SOCKS_SEND_ERR;
    }
    
    socks4_reply reply;

    if((ret = readFromSocket(client->sock, &reply, sizeof(reply))) != sizeof(reply)) {
        socks_log(client, __FUNCTION__, "failed to receive reply from socks server '%s:%d'", client->config->ip, client->config->port);
        return SOCKS_RECV_ERR;
    }

    switch(reply.code) {
        case SOCKS4_REPLY_GRANTED:
            socks_log(client, __FUNCTION__, "connection to '%s:%d' established", client->ip, client->port);
            return 0;

        case SOCKS4_REPLY_FAILED:
            socks_log(client, __FUNCTION__, "connection to '%s:%d' denied", client->ip, client->port);
            return SOCKS_REQUEST_DENY;

        default:
            socks_log(client, __FUNCTION__, "received unsupported reply code '%d' (X'%x')", reply.code, reply.code);
            return SOCKS_REPLY_INVAL;
    }

}

socks_init.c

代码语言:javascript
复制
//
// Created by flo on 4/17/23.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/ioctl.h>

#include "include/socks_internal.h"


int socks_init(socks *socks_handle, socks_config *config)
{
    int rc = 0;
    int on = 1;

    socks_handle->config = config;

    if(config->filename)
    {
        socks_handle->log = fopen(config->filename, "w");

        if( !socks_handle->log ) {
            fprintf(stderr, "error: can't open logfile '%s'\n", config->filename);
            return SOCKS_LOGCREAT_ERR;
        }

    }

    socks_log(socks_handle, __FUNCTION__, "Socks%d-%s started.", config->version, (config->type == SOCKS_SERVER) ? "Server" : "Client");

    // initialize socks main socket
    socks_handle->sock = socket(AF_INET, SOCK_STREAM, 0);
    if(socks_handle->sock < 0)
    {
        socks_log(socks_handle, __FUNCTION__, "failed to create socket");
        return SOCKS_SOCKCREAT_ERR; // SOCKS_SOCKET_CREATION_ERROR
    }


    /* this is SOCKS VERSION dependent */
    struct sockaddr_in inaddr;
    memset(&inaddr, 0, sizeof(inaddr));

    inaddr.sin_family = AF_INET;
    inaddr.sin_port = htons((uint16_t) config->port);
    inaddr.sin_addr.s_addr = inet_addr(config->ip);
    if(inaddr.sin_addr.s_addr == INADDR_NONE)
    {
        socks_log(socks_handle, __FUNCTION__, "failed to parse ip address %s", config->ip);
        return SOCKS_IP_INVAL; // SOCKS_WRONG_IP_ADDRESS
    }

    /* if server, bind address to socket and set it reusable */
    if(config->type == SOCKS_SERVER)
    {
        rc = setsockopt(socks_handle->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        if(rc < 0) {
            socks_log(socks_handle, __FUNCTION__, "failed to enable reuse of socket address");
            return SOCKS_SOCKREUSE_ERR;
        }

        if( bind(socks_handle->sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0 )
        {
            socks_log(socks_handle, __FUNCTION__, "failed to bind %s to socket.", config->ip);
            return SOCKS_SERVER_BIND_ERR; // SOCKS_BIND_ERROR
        }

    }

    /* if client, try to connect to proxy server */
    else if(config->type == SOCKS_CLIENT)
    {
        if(connect(socks_handle->sock, (struct sockaddr*)&inaddr, sizeof(inaddr)) != 0)
        {
            socks_log(socks_handle, __FUNCTION__, "failed to connect to socks server %s", config->ip);
            return SOCKS_CLIENT_CONNECT_ERR; // SOCKS_CLIENT_CONNECT_ERROR
        }
        socks_log(socks_handle, __FUNCTION__, "connected to socker server %s:%d", config->ip, config->port);
    }

    return 0;
}

/* sends data via proxy server */
ssize_t socks_send(socks *client, void *data, size_t len)
{
    return sendToSocket(client->sock, data, len);
}

/* reads data from proxy server */
ssize_t socks_recv(socks *client, void *data, size_t len)
{   
    return readFromSocket(client->sock, data, len);
}

socks_listen.c

代码语言:javascript
复制
#include <stdio.h>
#include <pthread.h>

#include "include/socks_internal.h"


int socks_listen(socks *proxy)
{
    if(proxy->config->type != SOCKS_SERVER) {
        socks_log(proxy, __FUNCTION__, "can not listen to socks handle. socks handle is a client");
        return -1;
    }

    pthread_t thread;
    socks_connection conn;
    int conidx = 0;

    if(listen(proxy->sock, proxy->config->maxconn) != 0) {
        socks_log(proxy, __FUNCTION__, "failed to listen on %s", proxy->config->ip);
        return SOCKS_LISTEN_ERR; // SOCKS_LISTEN_ERROR see: errno
    }

    socks_log(proxy, __FUNCTION__, "listening on '%s:%d'", proxy->config->ip, proxy->config->port);

    for(;;) {
        socks_connection conn;
        int ret = socks_accept(proxy, &conn);
        if(ret < 0) {
            // SOCKS_ACCEPT ERROR
            socks_log(proxy, __FUNCTION__, "failed to accept connection from %s", "unkown");
            continue;
        }

        pthread_create(&thread, NULL, socks_connection_thread, &conn);
        conidx++;
        
    }

    return 0;
}

socks_log.c

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <stdarg.h>
#include <errno.h>

#include "include/socks_internal.h"

int socks_log(socks *socks, const char *function, const char *fmt, ...)
{
    
    if(socks->log) {
        va_list args;
        va_start(args, fmt);
        time_t now = time(NULL); // get current time
        char buffer[128];
        struct tm tnow;

        gmtime_r(&now, &tnow); // create UTC time

        strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S-%Z", &tnow); // create UTC time format string

        fprintf(socks->log, "%s: ", buffer); // log timestamp
        vfprintf(socks->log, fmt, args); // log message

        if(errno) { // if errno is set, log calling function with error string
            fprintf(socks->log, "%s: %s", function, strerror(errno));
        }
        va_end(args);

        fputc('\n', socks->log);
        fflush(socks->log);
    }

    return 0;
}

thread.c

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <poll.h>

#include "include/socks_internal.h"

#define MAX_SOCKETS 2

#define DEFAULT_TIMEOUT (3 * 60 * 1000)

#define CLIENT_POLL 0
#define REMOTE_POLL 1

int timeout = DEFAULT_TIMEOUT;

void *socks_connection_thread(void *pipefd) {

    pthread_detach(pthread_self());
    socks_connection conn = *(socks_connection*) pipefd;
    int rc = 0;

    int timeout = DEFAULT_TIMEOUT;
    /* two pollfd's for listening on remote peer and client connection for events */
    struct pollfd pfds[MAX_SOCKETS];
    nfds_t nfds = MAX_SOCKETS;

    uint8_t client_buf[4096];
    size_t client_buf_size = 0;

    uint8_t target_buf[4096];
    size_t target_buf_size = 0;
    
    ssize_t num_bytes;

    memset(&pfds, 0, sizeof(pfds));

    /* set sockets to non blocking */
    int opt = 1;
    ioctl(conn.client_sock, FIONBIO, &opt);
    ioctl(conn.target_sock, FIONBIO, &opt);

    
    while(true) {
        

        pfds[CLIENT_POLL].fd = conn.client_sock;
        pfds[CLIENT_POLL].events = POLLIN;

        pfds[REMOTE_POLL].fd = conn.target_sock;
        pfds[REMOTE_POLL].events = POLLIN;


        /* waiting for some events */
        rc = poll(pfds, MAX_SOCKETS, timeout);
        if(rc < 0) {
            fprintf(stderr, "poll() failed: %s\n", strerror(errno));
            break;
        }

        if(rc == 0) {
            fprintf(stderr, "poll() timed out. End Connection\n");
            break;
        }


        /* there is something to read form the client side */
        if(pfds[CLIENT_POLL].revents & POLLIN)
        {

            num_bytes = readFromSocket(conn.client_sock, &client_buf[client_buf_size], sizeof(client_buf)-client_buf_size);
            if(num_bytes < 0) break; // client connection lost
            if(num_bytes > 0) {
                printf("read from client: %s (%ld)\n", client_buf, num_bytes);
                client_buf_size += num_bytes;
                pfds[REMOTE_POLL].revents = POLLOUT;
            }
        }
        /* there is something to read from the remote side */
        else if(pfds[REMOTE_POLL].revents & POLLIN)
        {
            //printf("Got data from remote.\n");
            num_bytes = readFromSocket(conn.target_sock, target_buf, sizeof(target_buf));
            if (num_bytes < 0) break; // remote connection lost
            if (num_bytes > 0) {
                printf("read from peer: %s (%ld)\n", target_buf, num_bytes);
                target_buf_size += num_bytes;
                pfds[CLIENT_POLL].revents = POLLOUT;
            }
        }

        /* client has data to receive */
        if(pfds[CLIENT_POLL].revents & POLLOUT) {
            num_bytes = sendToSocket(conn.client_sock, target_buf, target_buf_size);
            if (num_bytes < 0) break;
            if (num_bytes > 0) {
                printf("forward to client: %s (%ld)\n", target_buf, num_bytes);
                // remove the sent bytes...
                target_buf_size -= num_bytes;
                memmove(client_buf, &client_buf[num_bytes], client_buf_size);

            } 
        } else if(pfds[REMOTE_POLL].revents & POLLOUT) {
            num_bytes = sendToSocket(conn.target_sock, client_buf, num_bytes);
            if(num_bytes < 0) break;
            if(num_bytes > 0) {
                printf("forward to remote peer: %s (%ld)\n", client_buf, num_bytes);
                // remove the sent bytes...
                client_buf_size -= num_bytes;
                memmove(target_buf, &target_buf[num_bytes], target_buf_size);
            }

        }

        else {
            // unexpected event result appeared so close the connection
            break;
        }

    }

    // all done
    close(conn.client_sock);
    close(conn.target_sock);
    printf("Thread terminating\n");

}
EN

回答 3

Code Review用户

回答已采纳

发布于 2023-05-06 13:26:17

关键点

...我想回顾一下我的:

  • 项目结构

在我看来是明智的。您可以创建一个doc/目录,您可以在其中输入任何文档,比如socks4.rfc。

  • 文件化/代码分裂

你做的一切都很好。但是,只有一个socks.c文件包含现在分布在多个socks_*.c文件上的所有代码,并将readFromSocket()writeToSocket()socks_connection_thread()等实用程序函数放在utils.c文件中也是可以接受的。这是品味的问题。现在,编译器和代码编辑器处理大文件非常好。

不过,我会将include/socks.h重命名为include/SOCKS4 4.h,因为它实际上是SOCKS4 4特定的。

  • 结构命名/设计

这很好。不过,我不会费心于PACKED。这些变量已经被以这样一种方式布局,它们自然地被打包,没有必要以任何方式强制这样做。唯一的问题是uint8_t userid[1]字段。它不是真正的1字节,它实际上是一个可变长度字段,您目前在代码中没有处理它,我认为您认为它总是一个NUL字节吗?我会编写另一个函数,从套接字中读取可变长度的、以NUL结尾的字符串。

  • 错误处理

你在这么做,但这不一致。在某些函数中,您可以return错误代码,而在其他函数中,您可以执行kill(0, SIGTERM)。我会这样做,这样一切都会返回错误代码,终止进程是非常粗鲁的,并且阻止调用者以更好的方式处理错误。

还要考虑真正需要处理哪些错误。那么,如果不能设置SO_KEEPALIVE怎么办?它不会阻止您连接到SOCKS服务器。在这种情况下,忽略错误可能是最好的方法。不过,您可以考虑记录一个警告。

如果系统调用返回EAGAIN,则应该可以立即重新尝试它。因此,与其执行break,不如使用continue。对于EWOULDBLOCK来说,情况并非如此。

  • 当然还有密码。

其他人已经对此发表了评论,我没有什么要补充的,但请看下面。

  • 处理多个连接(每个会话的线程?线程池?)

这里有各种各样的权衡。您可以在没有线程的情况下完成它,只需要poll()所有的文件处理程序。如果您需要处理大量的通信量,那么使用多核并不会带来好处,但另一方面,您不必处理任何与线程相关的问题。

每个会话有一个线程是可以的,但是如果您有很多连接,则必须考虑每个线程的开销。特别是,每个线程的堆栈都需要内存,这可能非常多(在Linux上大约是2MB)。在32位系统上,这可能会导致您很快地耗尽(虚拟)内存。您可以使用p线程属性使用较小的堆栈启动线程来解决此问题。

线程池将是最复杂的解决方案。您打算在线程上分发连接吗?如果一个线程获得所有活动连接,而其他线程获得相对不活跃的连接,怎么办?还是每个线程都要去poll()所有的套接字?这可能会导致所有线程每次有活动时都会醒来。还是希望一个线程poll()s所有套接字,然后根据需要唤醒一个或多个线程来处理到达连接的实际数据?这需要大量的同步。

如果您希望它是多线程的,那么我会选择每个连接一个线程。这是最简单的,也是相当有效的,如果您记住堆栈空间,您可能会很好。

型安全

我试图创建一个灵活的socks_configsocks结构,但我不确定是否成功。我真的不想让socks4_clientsocks4_server为用户实现一个简单而小的界面,如果它是错误的“对象类型”,函数就会返回。

但是现在您依赖于代码来检查是否传入了正确的“对象类型”。但是,如果为此有单独的structs,编译器可以在编译时检查是否将正确的对象类型传递给函数。

请注意,您可以嵌套结构,这样就可以减少代码重复,如下所示:

代码语言:javascript
复制
typedef struct socks_common_config {
    char *ip;
    uint16_t port;
    char *filename;
    uint8_t version;
} socks_common_config;

typedef struct socks_server_config {
    int maxconn;
    socks_common_config common;
} socks_server_config;

typedef struct socks_client_config {
    socks_common_config common;
} socks_client_config;

不需要struct sock

与其拥有一个socks_init()函数,然后是socks_connect()socks_listen(),不如直接将配置传递给后两个函数?

代码语言:javascript
复制
int socks_connect(socks_client_config *config, char *ip, int port);
int socks_listen(socks_server_config *config);

不再需要socks结构了。int socks_connect()返回的只是已建立的代理连接的文件复制器(如果出现错误,则为-1 )。

票数 2
EN

Code Review用户

发布于 2023-04-29 20:43:26

只是一个小小的回顾。

测井时间

添加错误检查

time(), gmtime_r(), strftime()这样的函数都可以返回错误指示。使用它。

使用Z

使用"-%Z"来追加一个'-',然后时区名称就很奇怪了。我不喜欢在ISO 8601中支持这种格式。

常见的是为UTC时间附加一个"Z"

缓冲器大小

一个慷慨的缓冲区大小是好的,但我们可以减少它的一种方式,但仍然保持2倍的因素。

标准代码

gmtime_r()不是标准C库的一部分。例如,下面的备选词使用gmtime()

备用码

代码语言:javascript
复制
    #define TIME_BUF_LEN (11 /* year */ + 5*2 /* mdHMS */ + 6 /* separators */ )
    char buffer[TIME_BUF_LEN * 2 + 1];

    time_t now = time(NULL); // get current time
    if (now == -1) {
      strcpy(buffer, "Unknown time");
    } else {
      struct tm *tm_now = gmtime(&now);
      if (tm_now == NULL) {
        strcpy(buffer, "Unknown time");
      } else {
        if (strftime(buffer, sizeof buffer, "%Y-%m-%dT%H:%M:%SZ", tm_now) == 0) {
          strcpy(buffer, "Unknown time");
        }
      }
    }

编辑

潜伏虫

sendToSocket()中,代码在send()之后对errno进行测试,但并不首先清除errno。更好的代码将使其为零,即使在此应用程序中,sendToSocket()并不是与errno != 0一起调用的,以允许更好的将来使用。与许多函数一样,send()在成功时不会使errno为零。

代码语言:javascript
复制
    errno = 0; // Add
    num_bytes = send(sock, &pbuf[total], size-total, 0);
    if (num_bytes < 0) {
        if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {

或者,应该记录sendToSocket(),说明它不应该与errno != 0一起调用。

票数 5
EN

Code Review用户

发布于 2023-04-30 09:17:38

添加警卫:

代码语言:javascript
复制
#if defined(__GNUC__) || defined(__clang__)
#define PACKED __attribute__((__packed__))
#else 
#define PACKED /* If only */
#endif /* __GNUC__ || __clang__

不需要

强制转换:

代码语言:javascript
复制
// char *pbuf = (char*) buf;
char *pbuf = buf;

在C中有一个与void *之间的隐式转换。

减少复制:

代码语言:javascript
复制
void socket_enable_keepalive(int sock) {
#if 0
    int yes = 1;
    check(setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1);

    int idle = 1;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int)) != -1);

    int interval = 1;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(int)) != -1);

    int maxpkt = 10;
    check(setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(int)) != -1);
#endif

struct option {
        int level;
        int opt_name;
        const void *opt_val;
    } const options[] = {
        { SOL_SOCKET, SO_KEEPALIVE, (int[]) { 1 } },
        { IPPROTO_TCP, TCP_KEEPCNT, (int[]) { 10 } },
        { IPPROTO_TCP, TCP_KEEPIDLE, (int[]) { 1 } },
        { IPPROTO_TCP, TCP_KEEPINTVL, (int[]) { 1 } },
    };

    for (size_t i = 0; i < ARRAY_CARDINALITY (options); i++) {
         check (setsockopt (slave_fd, options[i].level, options[i].opt_name,
                options[i].opt_val, sizeof (int)) != -1);
    }

readFromSocket()sendToSocket()可以是单个宏:

代码语言:javascript
复制
#define gen_func(_name_, _calling__) \
ssize_t _name_(int sock, void *buf, size_t size) \
{   char *pbuf = buf; \
    ssize_t num_bytes, total = 0; \
                                  \
    while (total < size) { \
        num_bytes = _calling_(sock, &pbuf[total], size-total, 0); \
        if (num_bytes <= 0) { \
            if ((num_bytes < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) { \
                break; \
            } \       
            return -1; \
        } \
        total += num_bytes; \
    } \
    return total; } \

gen_func(readFromSocket, recv);
gen_func(sendToSocket, send);

/* There might be a bug in the above code. I didn't test it. */

socket_peer_addrinfo()socket_local_addrinfo()也可以这样做。

sendToSocket()中,我们应该检查if (num_bytes <= 0)而不是if (num_bytes < 0)

检查所有库/系统调用的返回值:

代码语言:javascript
复制
/* Why do these go unchecked? */
getsockname(sock, (struct sockaddr*) &inaddr, &inaddr_len);
getpeername(sock, (struct sockaddr*) &inaddr, &inaddr_len);
ioctl(conn.client_sock, FIONBIO, &opt);
ioctl(conn.target_sock, FIONBIO, &opt);

/* And all the calls to close(), though you may forego checking them. */

我更喜欢使用fcntl()而不是ioctl()

简化初始化:

代码语言:javascript
复制
#if 0
    struct sockaddr_in inaddr;
    memset(&inaddr, 0, sizeof(inaddr));

    inaddr.sin_family = AF_INET;
    inaddr.sin_addr.s_addr = req->ip;
    inaddr.sin_port = req->port
#else  
    struct sockaddr_in inaddr = { .sin_family = AF_INET, .sin_addr.s_addr = req->ip, .sin_port = req->port };
#endif

#if 0
    socks4_request request;

    request.version = SOCKS4_VERSION;
    request.cmd = SOCKS4_CMD_CONNECT;
    request.port = htons((short) port);
    request.ip = inet_addr(ip);
    request.userid[0] = 0x00;
#else
    socks4_request request = { .version = SOCKS4_VERSION, .cmd = SOCKS4_CMD_CONNECT, ...};
#endif

// In at least 2 more places. 

声明使用的变量:

代码语言:javascript
复制
#if 0 

    socks4_reply reply;
    ....
    if(connect(*target_sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) {
        reply.version = SOCKS4_VERSION;
        reply.code = SOCKS4_REPLY_FAILED;
        reply.ip = inaddr.sin_addr.s_addr;
        reply.port = inaddr.sin_port;
#else
   if (connect(*target_sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) {   
       socks4_reply reply = { .version = SOCKS4_VERSION, .code = SOCKS4_REPLY_FAILED, .ip = inaddr.sin_addr.s_addr, .port = inaddr.sin_port };
#endif

到函数的顶部查看变量被声明为什么很麻烦。

警告:变量timeout:

的多个声明

代码语言:javascript
复制
/* It was first declared here. */
int timeout = DEFAULT_TIMEOUT;

void *socks_connection_thread(void *pipefd) {

    pthread_detach(pthread_self());
    socks_connection conn = *(socks_connection*) pipefd;
    int rc = 0;

    int timeout = DEFAULT_TIMEOUT;
    /* Why declare it here again? */  

小调:

  • 不要留下那么多的线饲料,它们不生长在树上。
  • 运行拼写-检查代码。
  • socks_accept()应该只接受一个连接。它不应该从套接字读取,构建一个回复,并将其发送回客户端。
  • 下面这条错误消息没有多大帮助。考虑调用perror()sterror()
代码语言:javascript
复制
fprintf(stderr, "error: can't open logfile '%s'\n", config->filename);
  • 与你的支撑保持一致。
  • 如果不检查错误代码,请不要费心返回错误代码。socket_peer_addrinfo()应该检查它的值。
  • 我会重新处理日志实用程序,用snprintf()构建一个字符串,并对fprintf()/fputs()进行一次调用。
票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/284691

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档