我实现了一个基本的Socks4客户机和服务器,它现在只能处理CONNECT请求,也没有身份验证协议支持。
我已经用一个简单的ncat回显服务器对它进行了测试,但我想回顾一下我的:
我试图创建一个灵活的socks_config和socks结构,但我不确定是否成功。我真的不想让socks4_client和socks4_server为用户实现一个简单而小的界面,如果它是错误的“对象类型”,函数就会返回。
我读过关于使用轮询和选择与许多连接,这是不推荐的,所以我坚持线程的方式,但线程创建是昂贵的。很高兴听到你认为对的事。
我也不确定我的民意测验是愚蠢的还是正确的,或者可能更好。
正如您所注意到的,accept函数很长并且不分裂。那是因为我不知道该怎么分它。但也许你有一些提示给我。
在编写这篇文章时,我意识到accept函数是阻塞的,直到客户机发送一个连接请求。那么,我应该在accept()调用之后将所有过程添加到线程函数中吗?那么我的接受函数没有必要吗?
我有一个回购,可在:https://github.com/mortytheshorty/socks
这是我的项目结构:
├── 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
//
// 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_Hsocket_config.h
#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);
#endifsocks4.h
//
// 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_Hsocks_internal.h
#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, ...);
#endifreadFromSocket.c
#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
#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
#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
#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
#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
#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
//
// 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
#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
#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
#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");
}发布于 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_config和socks结构,但我不确定是否成功。我真的不想让socks4_client和socks4_server为用户实现一个简单而小的界面,如果它是错误的“对象类型”,函数就会返回。
但是现在您依赖于代码来检查是否传入了正确的“对象类型”。但是,如果为此有单独的structs,编译器可以在编译时检查是否将正确的对象类型传递给函数。
请注意,您可以嵌套结构,这样就可以减少代码重复,如下所示:
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(),不如直接将配置传递给后两个函数?
int socks_connect(socks_client_config *config, char *ip, int port);
int socks_listen(socks_server_config *config);不再需要socks结构了。int socks_connect()返回的只是已建立的代理连接的文件复制器(如果出现错误,则为-1 )。
发布于 2023-04-29 20:43:26
只是一个小小的回顾。
测井时间
像time(), gmtime_r(), strftime()这样的函数都可以返回错误指示。使用它。
Z使用"-%Z"来追加一个'-',然后时区名称就很奇怪了。我不喜欢在ISO 8601中支持这种格式。
常见的是为UTC时间附加一个"Z"。
一个慷慨的缓冲区大小是好的,但我们可以减少它的一种方式,但仍然保持2倍的因素。
gmtime_r()不是标准C库的一部分。例如,下面的备选词使用gmtime()。
#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为零。
errno = 0; // Add
num_bytes = send(sock, &pbuf[total], size-total, 0);
if (num_bytes < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {或者,应该记录sendToSocket(),说明它不应该与errno != 0一起调用。
发布于 2023-04-30 09:17:38
#if defined(__GNUC__) || defined(__clang__)
#define PACKED __attribute__((__packed__))
#else
#define PACKED /* If only */
#endif /* __GNUC__ || __clang__不需要
// char *pbuf = (char*) buf;
char *pbuf = buf;在C中有一个与void *之间的隐式转换。
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()可以是单个宏:
#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)。
/* 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()。
#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. #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:的多个声明
/* 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()。fprintf(stderr, "error: can't open logfile '%s'\n", config->filename);socket_peer_addrinfo()应该检查它的值。snprintf()构建一个字符串,并对fprintf()/fputs()进行一次调用。https://codereview.stackexchange.com/questions/284691
复制相似问题