Add initial socks5nio proxy implementation
Co-authored-by: Ezequiel Bellver <ebellver@itba.edu.ar> Co-authored-by: Juan Barmasch <jbarmasch@itba.edu.ar>
This commit is contained in:
parent
c2b410ab01
commit
bbe23ecbd6
|
@ -6,10 +6,16 @@
|
|||
.viminfo
|
||||
.vscode/
|
||||
|
||||
## Folders
|
||||
patches/
|
||||
test/
|
||||
|
||||
## Output
|
||||
server
|
||||
client
|
||||
socks5d
|
||||
*.o
|
||||
a.out
|
||||
|
||||
## Tests
|
||||
PVS-Studio.log
|
||||
report.tasks
|
||||
strace_out
|
||||
|
|
34
Makefile
34
Makefile
|
@ -1,32 +1,30 @@
|
|||
CC = gcc
|
||||
# CC = clang
|
||||
CCFLAGS = -std=c11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-implicit-fallthrough -pedantic -pedantic-errors -fsanitize=address -g -D_POSIX_C_SOURCE=200809L
|
||||
CCFLAGS = -std=c11 -Wall -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough -pedantic -pedantic-errors -fsanitize=address -g -D_POSIX_C_SOURCE=200809L
|
||||
LDFLAGS = -lpthread
|
||||
|
||||
SERVER_SOURCES=src/server.c src/args.c
|
||||
CLIENT_SOURCES=src/client.c
|
||||
SERVER_LIBS=include/server.h include/args.h
|
||||
CLIENT_LIBS=include/client.h
|
||||
SERVER_OBJ=server
|
||||
CLIENT_OBJ=client
|
||||
SERVER_SOURCES=src/args.c src/selector.c src/socks5nio.c src/stm.c src/hello.c src/request.c src/buffer.c src/server.c
|
||||
SERVER_LIBS=include/args.h include/selector.h include/socks5nio.h include/stm.h include/hello.h include/request.h include/buffer.h include/server.h
|
||||
SERVER_OBJECTS=$(SERVER_SOURCES:.c=.o)
|
||||
SERVER_TARGET=socks5d
|
||||
|
||||
all: $(CLIENT_OBJ) $(SERVER_OBJ)
|
||||
all: $(SERVER_OBJECTS) $(SERVER_TARGET)
|
||||
|
||||
$(SERVER_OBJ): $(SERVER_SOURCES) $(SERVER_LIBS)
|
||||
$(CC) $(CCFLAGS) -I ./include -o $(SERVER_OBJ) $(SERVER_SOURCES)
|
||||
%.o : %.c
|
||||
$(CC) $(CCFLAGS) $(LDFLAGS) -I ./include -c $< -o $@
|
||||
|
||||
$(CLIENT_OBJ): $(CLIENT_SOURCES) $(CLIENT_LIBS)
|
||||
$(CC) $(CCFLAGS) -I ./include -o $(CLIENT_OBJ) $(CLIENT_SOURCES)
|
||||
$(SERVER_TARGET): $(SERVER_OBJECTS)
|
||||
$(CC) $(CCFLAGS) $(LDFLAGS) -I ./include -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f $(CLIENT_OBJ) $(SERVER_OBJ)
|
||||
rm -f $(SERVER_OBJECTS) $(SERVER_TARGET)
|
||||
|
||||
test:
|
||||
test: clean
|
||||
pvs-studio-analyzer trace -- make
|
||||
pvs-studio-analyzer analyze
|
||||
plog-converter -a '64:1,2,3;GA:1,2,3;OP:1,2,3' -t tasklist -o report.tasks PVS-Studio.log
|
||||
cppcheck --quiet --enable=all --force --inconclusive .
|
||||
rm -f PVS-Studio.log
|
||||
cppcheck --quiet --enable=all --force --inconclusive -I include .
|
||||
|
||||
cleanTest:
|
||||
rm -f PVS-Studio.log report.tasks strace_out
|
||||
rm -f report.tasks strace_out
|
||||
|
||||
.PHONY: all clean test cleanTest
|
||||
|
|
|
@ -6,23 +6,22 @@
|
|||
#define MAX_USERS 500
|
||||
|
||||
struct users {
|
||||
char *name;
|
||||
char *pass;
|
||||
char * name;
|
||||
char * pass;
|
||||
};
|
||||
|
||||
struct socks5args {
|
||||
char *socks_addr;
|
||||
unsigned short socks_port;
|
||||
char * socks_addr;
|
||||
unsigned short socks_port;
|
||||
char * mng_addr;
|
||||
unsigned short mng_port;
|
||||
|
||||
char * mng_addr;
|
||||
unsigned short mng_port;
|
||||
bool disectors_enabled;
|
||||
|
||||
bool disectors_enabled;
|
||||
|
||||
struct users users[MAX_USERS];
|
||||
struct users users[MAX_USERS];
|
||||
};
|
||||
|
||||
void parse_args(const int argc, char **argv, struct socks5args *args);
|
||||
void parse_args(const int argc, char ** argv, struct socks5args * args);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -83,67 +83,55 @@
|
|||
*/
|
||||
typedef struct buffer buffer;
|
||||
struct buffer {
|
||||
uint8_t *data;
|
||||
uint8_t * data;
|
||||
|
||||
/** límite superior del buffer. inmutable */
|
||||
uint8_t *limit;
|
||||
uint8_t * limit;
|
||||
|
||||
/** puntero de lectura */
|
||||
uint8_t *read;
|
||||
uint8_t * read;
|
||||
|
||||
/** puntero de escritura */
|
||||
uint8_t *write;
|
||||
uint8_t * write;
|
||||
};
|
||||
|
||||
/**
|
||||
* inicializa el buffer sin utilizar el heap
|
||||
*/
|
||||
void
|
||||
buffer_init(buffer *b, const size_t n, uint8_t *data);
|
||||
void buffer_init(buffer * b, const size_t n, uint8_t * data);
|
||||
|
||||
/**
|
||||
* Retorna un puntero donde se pueden escribir hasta `*nbytes`.
|
||||
* Se debe notificar mediante la función `buffer_write_adv'
|
||||
*/
|
||||
uint8_t *
|
||||
buffer_write_ptr(buffer *b, size_t *nbyte);
|
||||
void
|
||||
buffer_write_adv(buffer *b, const ssize_t bytes);
|
||||
uint8_t * buffer_write_ptr(buffer * b, size_t * nbyte);
|
||||
void buffer_write_adv(buffer * b, const ssize_t bytes);
|
||||
|
||||
uint8_t *
|
||||
buffer_read_ptr(buffer *b, size_t *nbyte);
|
||||
void
|
||||
buffer_read_adv(buffer *b, const ssize_t bytes);
|
||||
uint8_t * buffer_read_ptr(buffer * b, size_t * nbyte);
|
||||
void buffer_read_adv(buffer *b, const ssize_t bytes);
|
||||
|
||||
/**
|
||||
* obtiene un byte
|
||||
*/
|
||||
uint8_t
|
||||
buffer_read(buffer *b);
|
||||
uint8_t buffer_read(buffer * b);
|
||||
|
||||
/** escribe un byte */
|
||||
void
|
||||
buffer_write(buffer *b, uint8_t c);
|
||||
void buffer_write(buffer * b, uint8_t c);
|
||||
|
||||
/**
|
||||
* compacta el buffer
|
||||
*/
|
||||
void
|
||||
buffer_compact(buffer *b);
|
||||
void buffer_compact(buffer * b);
|
||||
|
||||
/**
|
||||
* Reinicia todos los punteros
|
||||
*/
|
||||
void
|
||||
buffer_reset(buffer *b);
|
||||
void buffer_reset(buffer * b);
|
||||
|
||||
/** retorna true si hay bytes para leer del buffer */
|
||||
bool
|
||||
buffer_can_read(buffer *b);
|
||||
bool buffer_can_read(buffer * b);
|
||||
|
||||
/** retorna true si se pueden escribir bytes en el buffer */
|
||||
bool
|
||||
buffer_can_write(buffer *b);
|
||||
|
||||
bool buffer_can_write(buffer *b);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef HELLO_H
|
||||
#define HELLO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "buffer.h"
|
||||
|
||||
static const uint8_t METHOD_NO_AUTHENTICATION_REQURED = 0x00;
|
||||
static const uint8_t METHOD_NO_ACCEPTABLE_METHODS = 0xFF;
|
||||
|
||||
enum hello_state {
|
||||
hello_version,
|
||||
hello_nmethods,
|
||||
hello_methods,
|
||||
hello_done,
|
||||
hello_error_unsupported_version,
|
||||
};
|
||||
|
||||
struct hello_parser {
|
||||
void (* on_authentication_method) (struct hello_parser * parser, const uint8_t method);
|
||||
void * data;
|
||||
enum hello_state state;
|
||||
uint8_t remaining;
|
||||
};
|
||||
|
||||
void hello_parser_init(struct hello_parser *p);
|
||||
|
||||
enum hello_state hello_parser_feed(struct hello_parser * p, uint8_t b);
|
||||
|
||||
enum hello_state hello_consume(buffer * b, struct hello_parser * p, bool * errored);
|
||||
|
||||
bool hello_is_done(const enum hello_state state, bool * errored);
|
||||
|
||||
extern const char * hello_error(const struct hello_parser *p);
|
||||
|
||||
void hello_parser_close(struct hello_parser *p);
|
||||
|
||||
extern int hello_marshall(buffer * b, const uint8_t method);
|
||||
|
||||
#endif
|
|
@ -21,23 +21,4 @@ const char *
|
|||
sockaddr_to_human(char *buff, const size_t buffsize,
|
||||
const struct sockaddr *addr);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Escribe n bytes de buff en fd de forma bloqueante
|
||||
*
|
||||
* Retorna 0 si se realizó sin problema y errno si hubo problemas
|
||||
*/
|
||||
int
|
||||
sock_blocking_write(const int fd, buffer *b);
|
||||
|
||||
|
||||
/**
|
||||
* copia todo el contenido de source a dest de forma bloqueante.
|
||||
*
|
||||
* Retorna 0 si se realizó sin problema y errno si hubo problemas
|
||||
*/
|
||||
int
|
||||
sock_blocking_copy(const int source, const int dest);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
|
||||
/**
|
||||
* parser.c -- pequeño motor para parsers/lexers.
|
||||
*
|
||||
* El usuario describe estados y transiciones.
|
||||
* Las transiciones contienen una condición, un estado destino y acciones.
|
||||
*
|
||||
* El usuario provee al parser con bytes y éste retona eventos que pueden
|
||||
* servir para delimitar tokens o accionar directamente.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Evento que retorna el parser.
|
||||
* Cada tipo de evento tendrá sus reglas en relación a data.
|
||||
*/
|
||||
struct parser_event {
|
||||
/** tipo de evento */
|
||||
unsigned type;
|
||||
/** caracteres asociados al evento */
|
||||
uint8_t data[3];
|
||||
/** cantidad de datos en el buffer `data' */
|
||||
uint8_t n;
|
||||
|
||||
/** lista de eventos: si es diferente de null ocurrieron varios eventos */
|
||||
struct parser_event *next;
|
||||
};
|
||||
|
||||
/** describe una transición entre estados */
|
||||
struct parser_state_transition {
|
||||
/* condición: un caracter o una clase de caracter. Por ej: '\r' */
|
||||
int when;
|
||||
/** descriptor del estado destino cuando se cumple la condición */
|
||||
unsigned dest;
|
||||
/** acción 1 que se ejecuta cuando la condición es verdadera. requerida. */
|
||||
void (*act1)(struct parser_event *ret, const uint8_t c);
|
||||
/** otra acción opcional */
|
||||
void (*act2)(struct parser_event *ret, const uint8_t c);
|
||||
};
|
||||
|
||||
/** predicado para utilizar en `when' que retorna siempre true */
|
||||
static const unsigned ANY = 1 << 9;
|
||||
|
||||
/** declaración completa de una máquina de estados */
|
||||
struct parser_definition {
|
||||
/** cantidad de estados */
|
||||
const unsigned states_count;
|
||||
/** por cada estado, sus transiciones */
|
||||
const struct parser_state_transition **states;
|
||||
/** cantidad de estados por transición */
|
||||
const size_t *states_n;
|
||||
|
||||
/** estado inicial */
|
||||
const unsigned start_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* inicializa el parser.
|
||||
*
|
||||
* `classes`: caracterización de cada caracter (256 elementos)
|
||||
*/
|
||||
struct parser *
|
||||
parser_init (const unsigned *classes,
|
||||
const struct parser_definition *def);
|
||||
|
||||
/** destruye el parser */
|
||||
void
|
||||
parser_destroy (struct parser *p);
|
||||
|
||||
/** permite resetear el parser al estado inicial */
|
||||
void
|
||||
parser_reset (struct parser *p);
|
||||
|
||||
/**
|
||||
* el usuario alimenta el parser con un caracter, y el parser retorna un evento
|
||||
* de parsing. Los eventos son reusado entre llamadas por lo que si se desea
|
||||
* capturar los datos se debe clonar.
|
||||
*/
|
||||
const struct parser_event *
|
||||
parser_feed (struct parser *p, const uint8_t c);
|
||||
|
||||
/**
|
||||
* En caso de la aplicacion no necesite clases caracteres, se
|
||||
* provee dicho arreglo para ser usando en `parser_init'
|
||||
*/
|
||||
const unsigned *
|
||||
parser_no_classes(void);
|
||||
|
||||
|
||||
#endif
|
|
@ -1,39 +0,0 @@
|
|||
#ifndef PARSER_UTILS_H
|
||||
#define PARSER_UTILS_H
|
||||
|
||||
/*
|
||||
* parser_utils.c -- factory de ciertos parsers típicos
|
||||
*
|
||||
* Provee parsers reusables, como por ejemplo para verificar que
|
||||
* un string es igual a otro de forma case insensitive.
|
||||
*/
|
||||
#include "parser.h"
|
||||
|
||||
enum string_cmp_event_types {
|
||||
STRING_CMP_MAYEQ,
|
||||
/** hay posibilidades de que el string sea igual */
|
||||
STRING_CMP_EQ,
|
||||
/** NO hay posibilidades de que el string sea igual */
|
||||
STRING_CMP_NEQ,
|
||||
};
|
||||
|
||||
const char *
|
||||
parser_utils_strcmpi_event(const enum string_cmp_event_types type);
|
||||
|
||||
|
||||
/*
|
||||
* Crea un parser que verifica que los caracteres recibidos forment el texto
|
||||
* descripto por `s'.
|
||||
*
|
||||
* Si se recibe el evento `STRING_CMP_NEQ' el texto entrado no matchea.
|
||||
*/
|
||||
struct parser_definition
|
||||
parser_utils_strcmpi(const char *s);
|
||||
|
||||
/**
|
||||
* libera recursos asociado a una llamada de `parser_utils_strcmpi'
|
||||
*/
|
||||
void
|
||||
parser_utils_strcmpi_destroy(const struct parser_definition *p);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef REQUEST_H
|
||||
#define REQUEST_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
enum socks_req_cmd {
|
||||
socks_req_cmd_connect = 0x01,
|
||||
socks_req_cmd_bind = 0x02,
|
||||
socks_req_cmd_associate = 0x03,
|
||||
};
|
||||
|
||||
enum socks_addr_type {
|
||||
socks_req_addr_ipv4 = 0x01,
|
||||
socks_req_addr_domain = 0x03,
|
||||
socks_req_addr_ipv6 = 0x04,
|
||||
};
|
||||
|
||||
union socks_addr {
|
||||
char fqdn[0xff];
|
||||
struct sockaddr_in ipv4;
|
||||
struct sockaddr_in6 ipv6;
|
||||
};
|
||||
|
||||
struct request {
|
||||
enum socks_req_cmd cmd;
|
||||
enum socks_addr_type dest_addr_type;
|
||||
union socks_addr dest_addr;
|
||||
in_port_t dest_port;
|
||||
};
|
||||
|
||||
enum request_state {
|
||||
request_version,
|
||||
request_cmd,
|
||||
request_rsv,
|
||||
request_atyp,
|
||||
request_dstaddr_fqdn,
|
||||
request_dstaddr,
|
||||
request_dstport,
|
||||
request_done,
|
||||
request_error,
|
||||
request_error_unsupported_version,
|
||||
request_error_unsupported_atyp,
|
||||
};
|
||||
|
||||
struct request_parser {
|
||||
struct request * request;
|
||||
enum request_state state;
|
||||
|
||||
uint8_t n;
|
||||
uint8_t i;
|
||||
};
|
||||
|
||||
enum socks_response_status {
|
||||
status_succeeded = 0x00,
|
||||
status_general_SOCKS_server_failure = 0x01,
|
||||
status_connectino_not_allowed_by_ruleset = 0x02,
|
||||
status_network_unreachable = 0x03,
|
||||
status_host_unreachable = 0x04,
|
||||
status_connection_refused = 0x05,
|
||||
status_ttl_expired = 0x06,
|
||||
status_command_not_supported = 0x07,
|
||||
status_address_type_not_supported = 0x08,
|
||||
};
|
||||
|
||||
void request_parser_init(struct request_parser * p);
|
||||
|
||||
enum request_state request_consume(buffer * b, struct request_parser * p, bool * errored);
|
||||
|
||||
bool request_is_done(const enum request_state state, bool * errored);
|
||||
|
||||
void request_close(struct request_parser *p);
|
||||
|
||||
extern int request_marshall(buffer * b, const enum socks_response_status status);
|
||||
|
||||
enum socks_response_status errno_to_socks(int e);
|
||||
|
||||
enum socks_response_status cmd_resolve(struct request * request, struct sockaddr ** originaddr, socklen_t * origin_len, int * domain);
|
||||
|
||||
#endif
|
|
@ -101,7 +101,7 @@ typedef enum {
|
|||
OP_NOOP = 0,
|
||||
OP_READ = 1 << 0,
|
||||
OP_WRITE = 1 << 2,
|
||||
} fd_interest ;
|
||||
} fd_interest;
|
||||
|
||||
/**
|
||||
* Quita un interés de una lista de intereses
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "args.h"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef SOCKS5NIO_H
|
||||
#define SOCKS5NIO_H
|
||||
|
||||
void socksv5_passive_accept(struct selector_key * key);
|
||||
void socksv5_pool_destroy(void);
|
||||
|
||||
#endif
|
|
@ -34,7 +34,8 @@ struct state_machine {
|
|||
const struct state_definition *current;
|
||||
};
|
||||
|
||||
struct selector_key *key;
|
||||
// struct selector_key *key;
|
||||
#include "selector.h"
|
||||
|
||||
/**
|
||||
* definición de un estado de la máquina de estados
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
@ -5,7 +7,7 @@
|
|||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "../include/args.h"
|
||||
#include "args.h"
|
||||
|
||||
static unsigned short port(const char *s) {
|
||||
char *end = 0;
|
||||
|
@ -89,7 +91,7 @@ void parse_args(const int argc, char **argv, struct socks5args *args) {
|
|||
args->mng_port = port(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
if(nusers >= MAX_USERS) {
|
||||
if (nusers >= MAX_USERS) {
|
||||
fprintf(stderr, "Maximun number of command line users reached: %d.\n", MAX_USERS);
|
||||
exit(1);
|
||||
} else {
|
||||
|
|
20
src/buffer.c
20
src/buffer.c
|
@ -1,7 +1,5 @@
|
|||
/**
|
||||
* buffer.c - buffer con acceso directo (útil para I/O) que mantiene
|
||||
* mantiene puntero de lectura y de escritura.
|
||||
*/
|
||||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
@ -48,7 +46,7 @@ buffer_read_ptr(buffer *b, size_t *nbyte) {
|
|||
|
||||
inline void
|
||||
buffer_write_adv(buffer *b, const ssize_t bytes) {
|
||||
if(bytes > -1) {
|
||||
if (bytes > -1) {
|
||||
b->write += (size_t) bytes;
|
||||
assert(b->write <= b->limit);
|
||||
}
|
||||
|
@ -56,11 +54,11 @@ buffer_write_adv(buffer *b, const ssize_t bytes) {
|
|||
|
||||
inline void
|
||||
buffer_read_adv(buffer *b, const ssize_t bytes) {
|
||||
if(bytes > -1) {
|
||||
if (bytes > -1) {
|
||||
b->read += (size_t) bytes;
|
||||
assert(b->read <= b->write);
|
||||
|
||||
if(b->read == b->write) {
|
||||
if (b->read == b->write) {
|
||||
// compactacion poco costosa
|
||||
buffer_compact(b);
|
||||
}
|
||||
|
@ -70,7 +68,7 @@ buffer_read_adv(buffer *b, const ssize_t bytes) {
|
|||
inline uint8_t
|
||||
buffer_read(buffer *b) {
|
||||
uint8_t ret;
|
||||
if(buffer_can_read(b)) {
|
||||
if (buffer_can_read(b)) {
|
||||
ret = *b->read;
|
||||
buffer_read_adv(b, 1);
|
||||
} else {
|
||||
|
@ -81,7 +79,7 @@ buffer_read(buffer *b) {
|
|||
|
||||
inline void
|
||||
buffer_write(buffer *b, uint8_t c) {
|
||||
if(buffer_can_write(b)) {
|
||||
if (buffer_can_write(b)) {
|
||||
*b->write = c;
|
||||
buffer_write_adv(b, 1);
|
||||
}
|
||||
|
@ -89,9 +87,9 @@ buffer_write(buffer *b, uint8_t c) {
|
|||
|
||||
void
|
||||
buffer_compact(buffer *b) {
|
||||
if(b->data == b->read) {
|
||||
if (b->data == b->read) {
|
||||
// nada por hacer
|
||||
} else if(b->read == b->write) {
|
||||
} else if (b->read == b->write) {
|
||||
b->read = b->data;
|
||||
b->write = b->data;
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include "client.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "hello.h"
|
||||
|
||||
void hello_parser_init(struct hello_parser *p) {
|
||||
p->state = hello_version;
|
||||
p->remaining = 0;
|
||||
}
|
||||
|
||||
enum hello_state hello_parser_feed(struct hello_parser * p, uint8_t b) {
|
||||
switch (p->state) {
|
||||
case hello_version:
|
||||
if (0x05 == b) {
|
||||
p->state = hello_nmethods;
|
||||
} else {
|
||||
p->state = hello_error_unsupported_version;
|
||||
}
|
||||
break;
|
||||
case hello_nmethods:
|
||||
p->remaining = b;
|
||||
p->state = hello_methods;
|
||||
|
||||
if (p->remaining == 0) {
|
||||
p->state = hello_done;
|
||||
}
|
||||
break;
|
||||
case hello_methods:
|
||||
if (NULL != p->on_authentication_method) {
|
||||
p->on_authentication_method(p, b);
|
||||
}
|
||||
p->remaining--;
|
||||
if (p->remaining == 0) {
|
||||
p->state = hello_done;
|
||||
}
|
||||
break;
|
||||
case hello_done:
|
||||
case hello_error_unsupported_version:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown state %d\n", p->state);
|
||||
abort();
|
||||
}
|
||||
|
||||
return p->state;
|
||||
}
|
||||
|
||||
extern bool hello_is_done(const enum hello_state state, bool * errored) {
|
||||
bool ret = true;
|
||||
|
||||
switch (state) {
|
||||
case hello_error_unsupported_version:
|
||||
if (0 != errored) {
|
||||
*errored = true;
|
||||
}
|
||||
break;
|
||||
case hello_done:
|
||||
break;
|
||||
default:
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern const char * hello_error(const struct hello_parser *p) {
|
||||
char * ret;
|
||||
switch (p->state) {
|
||||
case hello_error_unsupported_version:
|
||||
ret = "unsupported version";
|
||||
break;
|
||||
default:
|
||||
ret = "";
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern void hello_parser_close(struct hello_parser * p) {}
|
||||
|
||||
|
||||
extern enum hello_state hello_consume(buffer * b, struct hello_parser *p, bool * errored) {
|
||||
enum hello_state st = p->state;
|
||||
|
||||
while (buffer_can_read(b)) {
|
||||
const uint8_t c = buffer_read(b);
|
||||
st = hello_parser_feed(p, c);
|
||||
if (hello_is_done(st, errored)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
extern int hello_marshall(buffer * b, const uint8_t method) {
|
||||
size_t n;
|
||||
uint8_t * buff = buffer_write_ptr(b, &n);
|
||||
if (n < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buff[0] = 0x05;
|
||||
buff[1] = method;
|
||||
|
||||
buffer_write_adv(b, 2);
|
||||
return 2;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
@ -13,7 +15,7 @@
|
|||
extern const char *
|
||||
sockaddr_to_human(char *buff, const size_t buffsize,
|
||||
const struct sockaddr *addr) {
|
||||
if(addr == 0) {
|
||||
if (addr == 0) {
|
||||
strncpy(buff, "null", buffsize);
|
||||
return buff;
|
||||
}
|
||||
|
@ -33,7 +35,7 @@ sockaddr_to_human(char *buff, const size_t buffsize,
|
|||
handled = true;
|
||||
break;
|
||||
}
|
||||
if(handled) {
|
||||
if (handled) {
|
||||
if (inet_ntop(addr->sa_family, p, buff, buffsize) == 0) {
|
||||
strncpy(buff, "unknown ip", buffsize);
|
||||
buff[buffsize - 1] = 0;
|
||||
|
@ -46,56 +48,10 @@ sockaddr_to_human(char *buff, const size_t buffsize,
|
|||
buff[buffsize - 1] = 0;
|
||||
const size_t len = strlen(buff);
|
||||
|
||||
if(handled) {
|
||||
if (handled) {
|
||||
snprintf(buff + len, buffsize - len, "%d", ntohs(port));
|
||||
}
|
||||
buff[buffsize - 1] = 0;
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
int
|
||||
sock_blocking_write(const int fd, buffer *b) {
|
||||
int ret = 0;
|
||||
ssize_t nwritten;
|
||||
size_t n;
|
||||
uint8_t *ptr;
|
||||
|
||||
do {
|
||||
ptr = buffer_read_ptr(b, &n);
|
||||
nwritten = send(fd, ptr, n, MSG_NOSIGNAL);
|
||||
if (nwritten > 0) {
|
||||
buffer_read_adv(b, nwritten);
|
||||
} else /* if (errno != EINTR) */ {
|
||||
ret = errno;
|
||||
break;
|
||||
}
|
||||
} while (buffer_can_read(b));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
sock_blocking_copy(const int source, const int dest) {
|
||||
int ret = 0;
|
||||
char buf[4096];
|
||||
ssize_t nread;
|
||||
while ((nread = recv(source, buf, N(buf), 0)) > 0) {
|
||||
char* out_ptr = buf;
|
||||
ssize_t nwritten;
|
||||
do {
|
||||
nwritten = send(dest, out_ptr, nread, MSG_NOSIGNAL);
|
||||
if (nwritten > 0) {
|
||||
nread -= nwritten;
|
||||
out_ptr += nwritten;
|
||||
} else /* if (errno != EINTR) */ {
|
||||
ret = errno;
|
||||
goto error;
|
||||
}
|
||||
} while (nread > 0);
|
||||
}
|
||||
error:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
91
src/parser.c
91
src/parser.c
|
@ -1,91 +0,0 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
/* CDT del parser */
|
||||
struct parser {
|
||||
/** tipificación para cada caracter */
|
||||
const unsigned *classes;
|
||||
/** definición de estados */
|
||||
const struct parser_definition *def;
|
||||
|
||||
/* estado actual */
|
||||
unsigned state;
|
||||
|
||||
/* evento que se retorna */
|
||||
struct parser_event e1;
|
||||
/* evento que se retorna */
|
||||
struct parser_event e2;
|
||||
};
|
||||
|
||||
void
|
||||
parser_destroy(struct parser *p) {
|
||||
if(p != NULL) {
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
struct parser *
|
||||
parser_init(const unsigned *classes,
|
||||
const struct parser_definition *def) {
|
||||
struct parser *ret = malloc(sizeof(*ret));
|
||||
if(ret != NULL) {
|
||||
memset(ret, 0, sizeof(*ret));
|
||||
ret->classes = classes;
|
||||
ret->def = def;
|
||||
ret->state = def->start_state;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
parser_reset(struct parser *p) {
|
||||
p->state = p->def->start_state;
|
||||
}
|
||||
|
||||
const struct parser_event *
|
||||
parser_feed(struct parser *p, const uint8_t c) {
|
||||
const unsigned type = p->classes[c];
|
||||
|
||||
p->e1.next = p->e2.next = 0;
|
||||
|
||||
const struct parser_state_transition *state = p->def->states[p->state];
|
||||
const size_t n = p->def->states_n[p->state];
|
||||
bool matched = false;
|
||||
|
||||
for(unsigned i = 0; i < n ; i++) {
|
||||
const int when = state[i].when;
|
||||
if (state[i].when <= 0xFF) {
|
||||
matched = (c == when);
|
||||
} else if(state[i].when == ANY) {
|
||||
matched = true;
|
||||
} else if(state[i].when > 0xFF) {
|
||||
matched = (type & when);
|
||||
} else {
|
||||
matched = false;
|
||||
}
|
||||
|
||||
if(matched) {
|
||||
state[i].act1(&p->e1, c);
|
||||
if(state[i].act2 != NULL) {
|
||||
p->e1.next = &p->e2;
|
||||
state[i].act2(&p->e2, c);
|
||||
}
|
||||
p->state = state[i].dest;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return &p->e1;
|
||||
}
|
||||
|
||||
|
||||
static const unsigned classes[0xFF] = {0x00};
|
||||
|
||||
const unsigned *
|
||||
parser_no_classes(void) {
|
||||
return classes;
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "parser_utils.h"
|
||||
|
||||
const char *
|
||||
parser_utils_strcmpi_event(const enum string_cmp_event_types type) {
|
||||
const char *ret;
|
||||
|
||||
switch(type) {
|
||||
case STRING_CMP_MAYEQ:
|
||||
ret = "wait(c)";
|
||||
break;
|
||||
case STRING_CMP_EQ:
|
||||
ret = "eq(c)";
|
||||
break;
|
||||
case STRING_CMP_NEQ:
|
||||
ret = "neq(c)";
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
may_eq(struct parser_event *ret, const uint8_t c) {
|
||||
ret->type = STRING_CMP_MAYEQ;
|
||||
ret->n = 1;
|
||||
ret->data[0] = c;
|
||||
}
|
||||
|
||||
static void
|
||||
eq(struct parser_event *ret, const uint8_t c) {
|
||||
ret->type = STRING_CMP_EQ;
|
||||
ret->n = 1;
|
||||
ret->data[0] = c;
|
||||
}
|
||||
|
||||
static void
|
||||
neq(struct parser_event *ret, const uint8_t c) {
|
||||
ret->type = STRING_CMP_NEQ;
|
||||
ret->n = 1;
|
||||
ret->data[0] = c;
|
||||
}
|
||||
|
||||
/*
|
||||
* para comparar "foo" (length 3) necesitamos 3 + 2 estados.
|
||||
* Los útimos dos, son el sumidero de comparación fallida, y
|
||||
* el estado donde se llegó a la comparación completa.
|
||||
*
|
||||
* static const struct parser_state_transition ST_0 [] = {
|
||||
* {.when = 'F', .dest = 1, .action1 = may_eq, },
|
||||
* {.when = 'f', .dest = 1, .action1 = may_eq, },
|
||||
* {.when = ANY, .dest = NEQ, .action1 = neq,},
|
||||
* };
|
||||
* static const struct parser_state_transition ST_1 [] = {
|
||||
* {.when = 'O', .dest = 2, .action1 = may_eq, },
|
||||
* {.when = 'o', .dest = 2, .action1 = may_eq, },
|
||||
* {.when = ANY, .dest = NEQ, .action1 = neq,},
|
||||
* };
|
||||
* static const struct parser_state_transition ST_2 [] = {
|
||||
* {.when = 'O', .dest = EQ, .action1 = eq, },
|
||||
* {.when = 'o', .dest = EQ, .action1 = eq, },
|
||||
* {.when = ANY, .dest = NEQ, .action1 = neq,},
|
||||
* };
|
||||
* static const struct parser_state_transition ST_EQ (3) [] = {
|
||||
* {.when = ANY, .dest = NEQ, .action1 = neq,},
|
||||
* };
|
||||
* static const struct parser_state_transition ST_NEQ (4) [] = {
|
||||
* {.when = ANY, .dest = NEQ, .action1 = neq,},
|
||||
* };
|
||||
*
|
||||
*/
|
||||
struct parser_definition
|
||||
parser_utils_strcmpi(const char *s) {
|
||||
const size_t n = strlen(s);
|
||||
|
||||
struct parser_state_transition **states = calloc(n + 2, sizeof(*states));
|
||||
size_t *nstates = calloc(n + 2, sizeof(*nstates));
|
||||
struct parser_state_transition *transitions= calloc(3 *(n + 2),
|
||||
sizeof(*transitions));
|
||||
if(states == NULL || nstates == NULL || transitions == NULL) {
|
||||
free(states);
|
||||
free(nstates);
|
||||
free(transitions);
|
||||
|
||||
struct parser_definition def = {
|
||||
.start_state = 0,
|
||||
.states_count = 0,
|
||||
.states = NULL,
|
||||
.states_n = NULL,
|
||||
};
|
||||
return def;
|
||||
}
|
||||
|
||||
// estados fijos
|
||||
const size_t st_eq = n;
|
||||
const size_t st_neq = n + 1;
|
||||
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
const size_t dest = (i + 1 == n) ? st_eq : i + 1;
|
||||
|
||||
transitions[i * 3 + 0].when = tolower(s[i]);
|
||||
transitions[i * 3 + 0].dest = dest;
|
||||
transitions[i * 3 + 0].act1 = i + 1 == n ? eq : may_eq;
|
||||
transitions[i * 3 + 1].when = toupper(s[i]);
|
||||
transitions[i * 3 + 1].dest = dest;
|
||||
transitions[i * 3 + 1].act1 = i + 1 == n ? eq : may_eq;
|
||||
transitions[i * 3 + 2].when = ANY;
|
||||
transitions[i * 3 + 2].dest = st_neq;
|
||||
transitions[i * 3 + 2].act1 = neq;
|
||||
states [i] = transitions + (i * 3 + 0);
|
||||
nstates [i] = 3;
|
||||
}
|
||||
// EQ
|
||||
transitions[(n + 0) * 3].when = ANY;
|
||||
transitions[(n + 0) * 3].dest = st_neq;
|
||||
transitions[(n + 0) * 3].act1 = neq;
|
||||
states [(n + 0)] = transitions + ((n + 0) * 3 + 0);
|
||||
nstates [(n + 0)] = 1;
|
||||
// NEQ
|
||||
transitions[(n + 1) * 3].when = ANY;
|
||||
transitions[(n + 1) * 3].dest = st_neq;
|
||||
transitions[(n + 1) * 3].act1 = neq;
|
||||
states [(n + 1)] = transitions + ((n + 1) * 3 + 0);
|
||||
nstates [(n + 1)] = 1;
|
||||
|
||||
|
||||
struct parser_definition def = {
|
||||
.start_state = 0,
|
||||
.states_count = n + 2,
|
||||
.states = (const struct parser_state_transition **) states,
|
||||
.states_n = (const size_t *) nstates,
|
||||
};
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
void
|
||||
parser_utils_strcmpi_destroy(const struct parser_definition *p) {
|
||||
free((void *)p->states[0]);
|
||||
free((void *)p->states);
|
||||
free((void *)p->states_n);
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "request.h"
|
||||
|
||||
static void remaining_set(struct request_parser * p, const int n) {
|
||||
p->i = 0;
|
||||
p->n = n;
|
||||
}
|
||||
|
||||
static int remaining_is_done(struct request_parser * p) {
|
||||
return p->i >= p->n;
|
||||
}
|
||||
|
||||
static enum request_state version(const uint8_t c, struct request_parser * p) {
|
||||
enum request_state next;
|
||||
switch (c) {
|
||||
case 0x05:
|
||||
next = request_cmd;
|
||||
break;
|
||||
default:
|
||||
next = request_error_unsupported_version;
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
static enum request_state cmd(const uint8_t c, struct request_parser * p) {
|
||||
p->request->cmd = c;
|
||||
return request_rsv;
|
||||
}
|
||||
|
||||
static enum request_state rsv(const uint8_t c, struct request_parser * p) {
|
||||
return request_atyp;
|
||||
}
|
||||
|
||||
static enum request_state atyp(const uint8_t c, struct request_parser * p) {
|
||||
enum request_state next;
|
||||
|
||||
p->request->dest_addr_type = c;
|
||||
switch (p->request->dest_addr_type) {
|
||||
case socks_req_addr_ipv4:
|
||||
remaining_set(p, 4);
|
||||
memset(&(p->request->dest_addr.ipv4), 0, sizeof(p->request->dest_addr.ipv4));
|
||||
p->request->dest_addr.ipv4.sin_family = AF_INET;
|
||||
next = request_dstaddr;
|
||||
break;
|
||||
case socks_req_addr_ipv6:
|
||||
remaining_set(p, 16);
|
||||
memset(&(p->request->dest_addr.ipv6), 0, sizeof(p->request->dest_addr.ipv6));
|
||||
p->request->dest_addr.ipv4.sin_family = AF_INET6;
|
||||
next = request_dstaddr;
|
||||
break;
|
||||
case socks_req_addr_domain:
|
||||
next = request_dstaddr_fqdn;
|
||||
break;
|
||||
default:
|
||||
next = request_error_unsupported_atyp;
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
static enum request_state dstaddr_fqdn(const uint8_t c, struct request_parser * p) {
|
||||
remaining_set(p, c);
|
||||
p->request->dest_addr.fqdn[p->n - 1] = 0;
|
||||
|
||||
return request_dstaddr;
|
||||
}
|
||||
|
||||
static enum request_state dstaddr(const uint8_t c, struct request_parser * p) {
|
||||
enum request_state next;
|
||||
|
||||
switch (p->request->dest_addr_type) {
|
||||
case socks_req_addr_ipv4:
|
||||
((uint8_t *)&(p->request->dest_addr.ipv4.sin_addr))[p->i++] = c;
|
||||
break;
|
||||
case socks_req_addr_ipv6:
|
||||
((uint8_t *)&(p->request->dest_addr.ipv6.sin6_addr))[p->i++] = c;
|
||||
break;
|
||||
case socks_req_addr_domain:
|
||||
p->request->dest_addr.fqdn[p->i++] = c;
|
||||
break;
|
||||
}
|
||||
if (remaining_is_done(p)) {
|
||||
remaining_set(p, 2);
|
||||
p->request->dest_port = 0;
|
||||
next = request_dstport;
|
||||
} else {
|
||||
next = request_dstaddr;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
#include <stdio.h>
|
||||
|
||||
static enum request_state dstport(const uint8_t c, struct request_parser * p) {
|
||||
enum request_state next;
|
||||
*(((uint8_t *) &(p->request->dest_port)) + p->i) = c;
|
||||
p->i++;
|
||||
next = request_dstport;
|
||||
if (p->i >= p->n) {
|
||||
next = request_done;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
void request_parser_init(struct request_parser * p) {
|
||||
p->state = request_version;
|
||||
memset(p->request, 0, sizeof(*(p->request)));
|
||||
}
|
||||
|
||||
static enum request_state request_parser_feed(struct request_parser * p, const uint8_t c) {
|
||||
enum request_state next;
|
||||
|
||||
switch(p->state) {
|
||||
case request_version:
|
||||
next = version(c, p);
|
||||
break;
|
||||
case request_cmd:
|
||||
next = cmd(c, p);
|
||||
break;
|
||||
case request_rsv:
|
||||
next = rsv(c, p);
|
||||
break;
|
||||
case request_atyp:
|
||||
next = atyp(c, p);
|
||||
break;
|
||||
case request_dstaddr_fqdn:
|
||||
next = dstaddr_fqdn(c, p);
|
||||
break;
|
||||
case request_dstaddr:
|
||||
next = dstaddr(c, p);
|
||||
break;
|
||||
case request_dstport:
|
||||
next = dstport(c, p);
|
||||
break;
|
||||
case request_done:
|
||||
case request_error:
|
||||
case request_error_unsupported_version:
|
||||
case request_error_unsupported_atyp:
|
||||
next = p->state;
|
||||
break;
|
||||
default:
|
||||
next = request_error;
|
||||
break;
|
||||
}
|
||||
|
||||
return p->state = next;
|
||||
}
|
||||
|
||||
bool request_is_done(const enum request_state state, bool * errored) {
|
||||
bool ret = true;
|
||||
|
||||
switch (state) {
|
||||
case request_error:
|
||||
case request_error_unsupported_version:
|
||||
case request_error_unsupported_atyp:
|
||||
if (0 != errored) {
|
||||
*errored = true;
|
||||
}
|
||||
break;
|
||||
case request_done:
|
||||
break;
|
||||
default:
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void request_close(struct request_parser *p) {}
|
||||
|
||||
extern enum request_state request_consume(buffer * b, struct request_parser *p, bool * errored) {
|
||||
enum request_state st = p->state;
|
||||
|
||||
while (buffer_can_read(b)) {
|
||||
const uint8_t c = buffer_read(b);
|
||||
st = request_parser_feed(p, c);
|
||||
if (request_is_done(st, errored)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
extern int request_marshall(buffer * b, const enum socks_response_status status) {
|
||||
size_t n;
|
||||
uint8_t * buff = buffer_write_ptr(b, &n);
|
||||
if (n < 10) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buff[0] = 0x05;
|
||||
buff[1] = status;
|
||||
buff[2] = 0x00;
|
||||
buff[3] = socks_req_addr_ipv4;
|
||||
buff[4] = 0x00;
|
||||
buff[5] = 0x00;
|
||||
buff[6] = 0x00;
|
||||
buff[7] = 0x00;
|
||||
buff[8] = 0x00;
|
||||
buff[9] = 0x00;
|
||||
|
||||
buffer_write_adv(b, 10);
|
||||
return 10;
|
||||
}
|
||||
|
||||
// TODO REPLACE CON sockaddr_storage
|
||||
enum socks_response_status cmd_resolve(struct request * request, struct sockaddr ** originaddr, socklen_t * origin_len, int * domain) {
|
||||
enum socks_response_status ret = status_general_SOCKS_server_failure;
|
||||
*domain = AF_INET;
|
||||
struct sockaddr * addr = 0x00;
|
||||
socklen_t addrlen = 0;
|
||||
|
||||
switch (request->dest_addr_type) {
|
||||
case socks_req_addr_domain: {
|
||||
// TODO: SOPORTAR DOMAIN QUE VIENE CON IPV6
|
||||
struct hostent * hp = gethostbyname(request->dest_addr.fqdn);
|
||||
if (hp == 0) {
|
||||
memset(&request->dest_addr, 0x00, sizeof(request->dest_addr));
|
||||
break;
|
||||
}
|
||||
request->dest_addr.ipv4.sin_family = hp->h_addrtype;
|
||||
memcpy((char *) &request->dest_addr.ipv4.sin_addr, *hp->h_addr_list, hp->h_length);
|
||||
}
|
||||
case socks_req_addr_ipv4:
|
||||
// *domain = AF_INET;
|
||||
addr = (struct sockaddr *)&(request->dest_addr.ipv4);
|
||||
addrlen = sizeof(request->dest_addr.ipv4);
|
||||
request->dest_addr.ipv4.sin_port = request->dest_port;
|
||||
break;
|
||||
case socks_req_addr_ipv6:
|
||||
*domain = AF_INET6;
|
||||
addr = (struct sockaddr *) &(request->dest_addr.ipv6);
|
||||
addrlen = sizeof(request->dest_addr.ipv6);
|
||||
request->dest_addr.ipv6.sin6_port = request->dest_port;
|
||||
break;
|
||||
default:
|
||||
return status_address_type_not_supported;
|
||||
}
|
||||
|
||||
*originaddr = addr;
|
||||
*origin_len = addrlen;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum socks_response_status errno_to_socks(int e) {
|
||||
enum socks_response_status ret = status_general_SOCKS_server_failure;
|
||||
switch (e) {
|
||||
case 0:
|
||||
ret = status_succeeded;
|
||||
break;
|
||||
case ECONNREFUSED:
|
||||
ret = status_connection_refused;
|
||||
break;
|
||||
case EHOSTUNREACH:
|
||||
ret = status_host_unreachable;
|
||||
break;
|
||||
case ENETUNREACH:
|
||||
ret = status_network_unreachable;
|
||||
break;
|
||||
case ETIMEDOUT:
|
||||
ret = status_ttl_expired;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
137
src/selector.c
137
src/selector.c
|
@ -1,14 +1,13 @@
|
|||
/**
|
||||
* selector.c - un muliplexor de entrada salida
|
||||
*/
|
||||
#include <stdio.h> // perror
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include <assert.h> // :)
|
||||
#include <errno.h> // :)
|
||||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <stdint.h> // SIZE_MAX
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -58,7 +57,7 @@ struct selector_init conf;
|
|||
static sigset_t emptyset, blockset;
|
||||
|
||||
selector_status
|
||||
selector_init(const struct selector_init *c) {
|
||||
selector_init(const struct selector_init *c) {
|
||||
memcpy(&conf, c, sizeof(conf));
|
||||
|
||||
// inicializamos el sistema de comunicación entre threads y el selector
|
||||
|
@ -74,7 +73,7 @@ selector_init(const struct selector_init *c) {
|
|||
// select
|
||||
sigemptyset(&blockset);
|
||||
sigaddset (&blockset, conf.signal);
|
||||
if(-1 == sigprocmask(SIG_BLOCK, &blockset, NULL)) {
|
||||
if (-1 == sigprocmask(SIG_BLOCK, &blockset, NULL)) {
|
||||
ret = SELECTOR_IO;
|
||||
goto finally;
|
||||
}
|
||||
|
@ -153,7 +152,7 @@ struct fdselector {
|
|||
/** protege el acceso a resolutions jobs */
|
||||
pthread_mutex_t resolution_mutex;
|
||||
/**
|
||||
* lista de trabajos blockeantes que finalizaron y que pueden ser
|
||||
* lista de trabajos bloqueantes que finalizaron y que pueden ser
|
||||
* notificados.
|
||||
*/
|
||||
struct blocking_job *resolution_jobs;
|
||||
|
@ -179,7 +178,7 @@ size_t next_capacity(const size_t n) {
|
|||
tmp = 1UL << bits;
|
||||
|
||||
assert(tmp >= n);
|
||||
if(tmp > ITEMS_MAX_SIZE) {
|
||||
if (tmp > ITEMS_MAX_SIZE) {
|
||||
tmp = ITEMS_MAX_SIZE;
|
||||
}
|
||||
|
||||
|
@ -211,8 +210,8 @@ items_max_fd(fd_selector s) {
|
|||
int max = 0;
|
||||
for(int i = 0; i <= s->max_fd; i++) {
|
||||
struct item *item = s->fds + i;
|
||||
if(ITEM_USED(item)) {
|
||||
if(item->fd > max) {
|
||||
if (ITEM_USED(item)) {
|
||||
if (item->fd > max) {
|
||||
max = item->fd;
|
||||
}
|
||||
}
|
||||
|
@ -225,39 +224,38 @@ items_update_fdset_for_fd(fd_selector s, const struct item * item) {
|
|||
FD_CLR(item->fd, &s->master_r);
|
||||
FD_CLR(item->fd, &s->master_w);
|
||||
|
||||
if(ITEM_USED(item)) {
|
||||
if(item->interest & OP_READ) {
|
||||
if (ITEM_USED(item)) {
|
||||
if (item->interest & OP_READ) {
|
||||
FD_SET(item->fd, &(s->master_r));
|
||||
}
|
||||
|
||||
if(item->interest & OP_WRITE) {
|
||||
if (item->interest & OP_WRITE) {
|
||||
FD_SET(item->fd, &(s->master_w));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* garantizar cierta cantidad de elemenos en `fds'.
|
||||
* Se asegura de que `n' sea un número que la plataforma donde corremos lo
|
||||
* garantizar cierta cantidad de elemenos en 'fds'.
|
||||
* Se asegura de que 'n' sea un número que la plataforma donde corremos lo
|
||||
* soporta
|
||||
*/
|
||||
static selector_status
|
||||
ensure_capacity(fd_selector s, const size_t n) {
|
||||
static selector_status ensure_capacity(fd_selector s, const size_t n) {
|
||||
selector_status ret = SELECTOR_SUCCESS;
|
||||
|
||||
const size_t element_size = sizeof(*s->fds);
|
||||
if(n < s->fd_size) {
|
||||
if (n < s->fd_size) {
|
||||
// nada para hacer, entra...
|
||||
ret = SELECTOR_SUCCESS;
|
||||
} else if(n > ITEMS_MAX_SIZE) {
|
||||
} else if (n > ITEMS_MAX_SIZE) {
|
||||
// me estás pidiendo más de lo que se puede.
|
||||
ret = SELECTOR_MAXFD;
|
||||
} else if(NULL == s->fds) {
|
||||
} else if (NULL == s->fds) {
|
||||
// primera vez.. alocamos
|
||||
const size_t new_size = next_capacity(n);
|
||||
|
||||
s->fds = calloc(new_size, element_size);
|
||||
if(NULL == s->fds) {
|
||||
if (NULL == s->fds) {
|
||||
ret = SELECTOR_ENOMEM;
|
||||
} else {
|
||||
s->fd_size = new_size;
|
||||
|
@ -266,14 +264,14 @@ ensure_capacity(fd_selector s, const size_t n) {
|
|||
} else {
|
||||
// hay que agrandar...
|
||||
const size_t new_size = next_capacity(n);
|
||||
if (new_size > SIZE_MAX/element_size) { // ver MEM07-C
|
||||
if (new_size > (SIZE_MAX / element_size)) { // ver MEM07-C
|
||||
ret = SELECTOR_ENOMEM;
|
||||
} else {
|
||||
struct item *tmp = realloc(s->fds, new_size * element_size);
|
||||
if(NULL == tmp) {
|
||||
if (NULL == tmp) {
|
||||
ret = SELECTOR_ENOMEM;
|
||||
} else {
|
||||
s->fds = tmp;
|
||||
s->fds = tmp;
|
||||
const size_t old_size = s->fd_size;
|
||||
s->fd_size = new_size;
|
||||
|
||||
|
@ -289,14 +287,14 @@ fd_selector
|
|||
selector_new(const size_t initial_elements) {
|
||||
size_t size = sizeof(struct fdselector);
|
||||
fd_selector ret = malloc(size);
|
||||
if(ret != NULL) {
|
||||
if (ret != NULL) {
|
||||
memset(ret, 0x00, size);
|
||||
ret->master_t.tv_sec = conf.select_timeout.tv_sec;
|
||||
ret->master_t.tv_nsec = conf.select_timeout.tv_nsec;
|
||||
assert(ret->max_fd == 0);
|
||||
ret->resolution_jobs = 0;
|
||||
pthread_mutex_init(&ret->resolution_mutex, 0);
|
||||
if(0 != ensure_capacity(ret, initial_elements)) {
|
||||
if (0 != ensure_capacity(ret, initial_elements)) {
|
||||
selector_destroy(ret);
|
||||
ret = NULL;
|
||||
}
|
||||
|
@ -307,20 +305,21 @@ selector_new(const size_t initial_elements) {
|
|||
void
|
||||
selector_destroy(fd_selector s) {
|
||||
// lean ya que se llama desde los casos fallidos de _new.
|
||||
if(s != NULL) {
|
||||
if(s->fds != NULL) {
|
||||
if (s != NULL) {
|
||||
if (s->fds != NULL) {
|
||||
for(size_t i = 0; i < s->fd_size ; i++) {
|
||||
if(ITEM_USED(s->fds + i)) {
|
||||
if (ITEM_USED(s->fds + i)) {
|
||||
selector_unregister_fd(s, i);
|
||||
}
|
||||
}
|
||||
pthread_mutex_destroy(&s->resolution_mutex);
|
||||
for(struct blocking_job *j = s->resolution_jobs; j != NULL;
|
||||
j = j->next) {
|
||||
for(struct blocking_job *next, *j = s->resolution_jobs; j != NULL; j = j->next) {
|
||||
next = j->next;
|
||||
free(j);
|
||||
j = next;
|
||||
}
|
||||
free(s->fds);
|
||||
s->fds = NULL;
|
||||
s->fds = NULL;
|
||||
s->fd_size = 0;
|
||||
}
|
||||
free(s);
|
||||
|
@ -330,29 +329,25 @@ selector_destroy(fd_selector s) {
|
|||
#define INVALID_FD(fd) ((fd) < 0 || (fd) >= ITEMS_MAX_SIZE)
|
||||
|
||||
selector_status
|
||||
selector_register(fd_selector s,
|
||||
const int fd,
|
||||
const fd_handler *handler,
|
||||
const fd_interest interest,
|
||||
void *data) {
|
||||
selector_register(fd_selector s, const int fd, const fd_handler *handler, const fd_interest interest, void *data) {
|
||||
selector_status ret = SELECTOR_SUCCESS;
|
||||
// 0. validación de argumentos
|
||||
if(s == NULL || INVALID_FD(fd) || handler == NULL) {
|
||||
if (s == NULL || INVALID_FD(fd) || handler == NULL) {
|
||||
ret = SELECTOR_IARGS;
|
||||
goto finally;
|
||||
}
|
||||
// 1. tenemos espacio?
|
||||
size_t ufd = (size_t)fd;
|
||||
if(ufd > s->fd_size) {
|
||||
if (ufd >= s->fd_size) {
|
||||
ret = ensure_capacity(s, ufd);
|
||||
if(SELECTOR_SUCCESS != ret) {
|
||||
if (SELECTOR_SUCCESS != ret) {
|
||||
goto finally;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. registración
|
||||
struct item * item = s->fds + ufd;
|
||||
if(ITEM_USED(item)) {
|
||||
if (ITEM_USED(item)) {
|
||||
ret = SELECTOR_FDINUSE;
|
||||
goto finally;
|
||||
} else {
|
||||
|
@ -362,7 +357,7 @@ selector_register(fd_selector s,
|
|||
item->data = data;
|
||||
|
||||
// actualizo colaterales
|
||||
if(fd > s->max_fd) {
|
||||
if (fd > s->max_fd) {
|
||||
s->max_fd = fd;
|
||||
}
|
||||
items_update_fdset_for_fd(s, item);
|
||||
|
@ -377,18 +372,18 @@ selector_unregister_fd(fd_selector s,
|
|||
const int fd) {
|
||||
selector_status ret = SELECTOR_SUCCESS;
|
||||
|
||||
if(NULL == s || INVALID_FD(fd)) {
|
||||
if (NULL == s || INVALID_FD(fd)) {
|
||||
ret = SELECTOR_IARGS;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
struct item *item = s->fds + fd;
|
||||
if(!ITEM_USED(item)) {
|
||||
if (!ITEM_USED(item)) {
|
||||
ret = SELECTOR_IARGS;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
if(item->handler->handle_close != NULL) {
|
||||
if (item->handler->handle_close != NULL) {
|
||||
struct selector_key key = {
|
||||
.s = s,
|
||||
.fd = item->fd,
|
||||
|
@ -412,12 +407,12 @@ selector_status
|
|||
selector_set_interest(fd_selector s, int fd, fd_interest i) {
|
||||
selector_status ret = SELECTOR_SUCCESS;
|
||||
|
||||
if(NULL == s || INVALID_FD(fd)) {
|
||||
if (NULL == s || INVALID_FD(fd)) {
|
||||
ret = SELECTOR_IARGS;
|
||||
goto finally;
|
||||
}
|
||||
struct item *item = s->fds + fd;
|
||||
if(!ITEM_USED(item)) {
|
||||
if (!ITEM_USED(item)) {
|
||||
ret = SELECTOR_IARGS;
|
||||
goto finally;
|
||||
}
|
||||
|
@ -431,7 +426,7 @@ selector_status
|
|||
selector_set_interest_key(struct selector_key *key, fd_interest i) {
|
||||
selector_status ret;
|
||||
|
||||
if(NULL == key || NULL == key->s || INVALID_FD(key->fd)) {
|
||||
if (NULL == key || NULL == key->s || INVALID_FD(key->fd)) {
|
||||
ret = SELECTOR_IARGS;
|
||||
} else {
|
||||
ret = selector_set_interest(key->s, key->fd, i);
|
||||
|
@ -453,21 +448,21 @@ handle_iteration(fd_selector s) {
|
|||
|
||||
for (int i = 0; i <= n; i++) {
|
||||
struct item *item = s->fds + i;
|
||||
if(ITEM_USED(item)) {
|
||||
if (ITEM_USED(item)) {
|
||||
key.fd = item->fd;
|
||||
key.data = item->data;
|
||||
if(FD_ISSET(item->fd, &s->slave_r)) {
|
||||
if(OP_READ & item->interest) {
|
||||
if(0 == item->handler->handle_read) {
|
||||
if (FD_ISSET(item->fd, &s->slave_r)) {
|
||||
if (OP_READ & item->interest) {
|
||||
if (0 == item->handler->handle_read) {
|
||||
assert(("OP_READ arrived but no handler. bug!" == 0));
|
||||
} else {
|
||||
item->handler->handle_read(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(FD_ISSET(i, &s->slave_w)) {
|
||||
if(OP_WRITE & item->interest) {
|
||||
if(0 == item->handler->handle_write) {
|
||||
if (FD_ISSET(i, &s->slave_w)) {
|
||||
if (OP_WRITE & item->interest) {
|
||||
if (0 == item->handler->handle_write) {
|
||||
assert(("OP_WRITE arrived but no handler. bug!" == 0));
|
||||
} else {
|
||||
item->handler->handle_write(&key);
|
||||
|
@ -484,18 +479,20 @@ handle_block_notifications(fd_selector s) {
|
|||
.s = s,
|
||||
};
|
||||
pthread_mutex_lock(&s->resolution_mutex);
|
||||
for(struct blocking_job *j = s->resolution_jobs;
|
||||
for(struct blocking_job *next, *j = s->resolution_jobs;
|
||||
j != NULL ;
|
||||
j = j->next) {
|
||||
|
||||
struct item *item = s->fds + j->fd;
|
||||
if(ITEM_USED(item)) {
|
||||
key.fd = item->fd;
|
||||
if (ITEM_USED(item)) {
|
||||
key.fd = item->fd;
|
||||
key.data = item->data;
|
||||
item->handler->handle_block(&key);
|
||||
}
|
||||
|
||||
next = j->next;
|
||||
free(j);
|
||||
j = next;
|
||||
}
|
||||
s->resolution_jobs = 0;
|
||||
pthread_mutex_unlock(&s->resolution_mutex);
|
||||
|
@ -509,7 +506,7 @@ selector_notify_block(fd_selector s,
|
|||
|
||||
// TODO(juan): usar un pool
|
||||
struct blocking_job *job = malloc(sizeof(*job));
|
||||
if(job == NULL) {
|
||||
if (job == NULL) {
|
||||
ret = SELECTOR_ENOMEM;
|
||||
goto finally;
|
||||
}
|
||||
|
@ -541,7 +538,7 @@ selector_select(fd_selector s) {
|
|||
|
||||
int fds = pselect(s->max_fd + 1, &s->slave_r, &s->slave_w, 0, &s->slave_t,
|
||||
&emptyset);
|
||||
if(-1 == fds) {
|
||||
if (-1 == fds) {
|
||||
switch(errno) {
|
||||
case EAGAIN:
|
||||
case EINTR:
|
||||
|
@ -551,8 +548,8 @@ selector_select(fd_selector s) {
|
|||
// ayuda a encontrar casos donde se cierran los fd pero no
|
||||
// se desregistraron
|
||||
for(int i = 0 ; i < s->max_fd; i++) {
|
||||
if(FD_ISSET(i, &s->master_r)|| FD_ISSET(i, &s->master_w)) {
|
||||
if(-1 == fcntl(i, F_GETFD, 0)) {
|
||||
if (FD_ISSET(i, &s->master_r)|| FD_ISSET(i, &s->master_w)) {
|
||||
if (-1 == fcntl(i, F_GETFD, 0)) {
|
||||
fprintf(stderr, "Bad descriptor detected: %d\n", i);
|
||||
}
|
||||
}
|
||||
|
@ -567,7 +564,7 @@ selector_select(fd_selector s) {
|
|||
} else {
|
||||
handle_iteration(s);
|
||||
}
|
||||
if(ret == SELECTOR_SUCCESS) {
|
||||
if (ret == SELECTOR_SUCCESS) {
|
||||
handle_block_notifications(s);
|
||||
}
|
||||
finally:
|
||||
|
@ -578,10 +575,10 @@ int
|
|||
selector_fd_set_nio(const int fd) {
|
||||
int ret = 0;
|
||||
int flags = fcntl(fd, F_GETFD, 0);
|
||||
if(flags == -1) {
|
||||
if (flags == -1) {
|
||||
ret = -1;
|
||||
} else {
|
||||
if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
|
129
src/server.c
129
src/server.c
|
@ -1,8 +1,133 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include "server.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include "args.h"
|
||||
#include "selector.h"
|
||||
#include "socks5nio.h"
|
||||
|
||||
static bool done = false;
|
||||
|
||||
static void sigterm_handler(const int signal) {
|
||||
printf("Signal %d, Cleaning up and exiting\n", signal);
|
||||
done = true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned port = 1080;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct socks5args * args = malloc(sizeof(struct socks5args));
|
||||
parse_args(argc, argv, args);
|
||||
free(args);
|
||||
|
||||
close(STDIN_FILENO);
|
||||
|
||||
const char *err_msg = NULL;
|
||||
selector_status ss = SELECTOR_SUCCESS;
|
||||
fd_selector selector = NULL;
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
const int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (server < 0) {
|
||||
err_msg = "Unable to create socket";
|
||||
goto finally;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Listening on TCP port %u\n", port);
|
||||
|
||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
|
||||
|
||||
if (bind(server, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
|
||||
err_msg = "Unable to bind socket";
|
||||
goto finally;
|
||||
}
|
||||
|
||||
if (listen(server, 20) < 0) {
|
||||
err_msg = "Unable to listen";
|
||||
goto finally;
|
||||
}
|
||||
|
||||
signal(SIGTERM, sigterm_handler);
|
||||
signal(SIGINT, sigterm_handler);
|
||||
|
||||
if (selector_fd_set_nio(server) == -1) {
|
||||
err_msg = "Getting server socket flags";
|
||||
goto finally;
|
||||
}
|
||||
const struct selector_init conf = {
|
||||
.signal = SIGALRM,
|
||||
.select_timeout = {
|
||||
.tv_sec = 10,
|
||||
.tv_nsec = 0,
|
||||
},
|
||||
};
|
||||
if (0 != selector_init(&conf)) {
|
||||
err_msg = "Initializing selector";
|
||||
goto finally;
|
||||
}
|
||||
|
||||
selector = selector_new(1024);
|
||||
if (selector == NULL) {
|
||||
err_msg = "Unable to create selector";
|
||||
goto finally;
|
||||
}
|
||||
|
||||
const struct fd_handler socksv5 = {
|
||||
.handle_read = socksv5_passive_accept,
|
||||
.handle_write = NULL,
|
||||
.handle_close = NULL,
|
||||
};
|
||||
ss = selector_register(selector, server, &socksv5, OP_READ, NULL);
|
||||
if (ss != SELECTOR_SUCCESS) {
|
||||
err_msg = "Registering fd";
|
||||
goto finally;
|
||||
}
|
||||
for(;!done;) {
|
||||
err_msg = NULL;
|
||||
ss = selector_select(selector);
|
||||
if (ss != SELECTOR_SUCCESS) {
|
||||
err_msg = "Serving";
|
||||
goto finally;
|
||||
}
|
||||
}
|
||||
if (err_msg == NULL) {
|
||||
err_msg = "Closing";
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
finally:
|
||||
if (ss != SELECTOR_SUCCESS) {
|
||||
fprintf(stderr, "%s: %s\n", (err_msg == NULL) ? "": err_msg, ss == SELECTOR_IO ? strerror(errno) : selector_error(ss));
|
||||
ret = 2;
|
||||
} else if (err_msg) {
|
||||
perror(err_msg);
|
||||
ret = 1;
|
||||
}
|
||||
if (selector != NULL) {
|
||||
selector_destroy(selector);
|
||||
}
|
||||
selector_close();
|
||||
|
||||
socksv5_pool_destroy();
|
||||
|
||||
if (server >= 0) {
|
||||
close(server);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,845 @@
|
|||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "hello.h"
|
||||
#include "request.h"
|
||||
#include "buffer.h"
|
||||
#include "selector.h"
|
||||
|
||||
#include "stm.h"
|
||||
#include "socks5nio.h"
|
||||
#include "netutils.h"
|
||||
|
||||
#define N(x) (sizeof(x)/sizeof((x)[0]))
|
||||
#define BUFF_SIZE 2048 // TODO: decidir tamaño del buffer
|
||||
|
||||
/** maquina de estados general */
|
||||
enum socks_v5state {
|
||||
HELLO_READ,
|
||||
HELLO_WRITE,
|
||||
|
||||
// AUTH_READ,
|
||||
// AUTH_WRITE,
|
||||
|
||||
REQUEST_READ,
|
||||
REQUEST_RESOLV,
|
||||
REQUEST_CONNECTING,
|
||||
REQUEST_WRITE,
|
||||
|
||||
COPY,
|
||||
|
||||
DONE,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Definición de variables para cada estado
|
||||
|
||||
/** usado por HELLO_READ, HELLO_WRITE */
|
||||
struct hello_st {
|
||||
/** buffer utilizado para I/O */
|
||||
buffer *rb, *wb;
|
||||
struct hello_parser parser;
|
||||
/** el método de autenticación seleccionado */
|
||||
uint8_t method;
|
||||
};
|
||||
|
||||
struct request_st {
|
||||
buffer *rb, *wb;
|
||||
|
||||
struct request request;
|
||||
struct request_parser parser;
|
||||
|
||||
enum socks_response_status status;
|
||||
|
||||
struct sockaddr_storage * origin_addr;
|
||||
socklen_t * origin_addr_len;
|
||||
int * origin_domain;
|
||||
|
||||
const int * client_fd;
|
||||
int * origin_fd;
|
||||
};
|
||||
|
||||
struct connecting {
|
||||
buffer * wb;
|
||||
const int * client_fd;
|
||||
int * origin_fd;
|
||||
enum socks_response_status * status;
|
||||
};
|
||||
|
||||
struct copy {
|
||||
int * fd;
|
||||
buffer * rb, * wb;
|
||||
fd_interest duplex;
|
||||
|
||||
struct copy * other;
|
||||
};
|
||||
|
||||
/*
|
||||
* Si bien cada estado tiene su propio struct que le da un alcance
|
||||
* acotado, disponemos de la siguiente estructura para hacer una única
|
||||
* alocación cuando recibimos la conexión.
|
||||
*
|
||||
* Se utiliza un contador de referencias (references) para saber cuando debemos
|
||||
* liberarlo finalmente, y un pool para reusar alocaciones previas.
|
||||
*/
|
||||
struct socks5 {
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len;
|
||||
int client_fd;
|
||||
|
||||
struct addrinfo * origin_resolution;
|
||||
struct addrinfo * origin_resolution_current;
|
||||
|
||||
struct sockaddr_storage origin_addr;
|
||||
socklen_t origin_addr_len;
|
||||
int origin_domain;
|
||||
int origin_fd;
|
||||
|
||||
/** maquinas de estados */
|
||||
struct state_machine stm;
|
||||
|
||||
/** estados para el client_fd */
|
||||
union {
|
||||
struct hello_st hello;
|
||||
struct request_st request;
|
||||
struct copy copy;
|
||||
} client;
|
||||
/** estados para el origin_fd */
|
||||
union {
|
||||
struct connecting conn;
|
||||
struct copy copy;
|
||||
} orig;
|
||||
|
||||
// TODO: decidir tamaño del buffer
|
||||
uint8_t raw_buff_a[BUFF_SIZE], raw_buff_b[BUFF_SIZE];
|
||||
buffer read_buffer, write_buffer;
|
||||
|
||||
unsigned references;
|
||||
|
||||
struct socks5 * next;
|
||||
};
|
||||
|
||||
static const unsigned max_pool = 50;
|
||||
static unsigned pool_size = 0;
|
||||
static struct socks5 * pool = 0;
|
||||
|
||||
static const struct state_definition * socks5_describe_states(void);
|
||||
|
||||
static struct socks5 * socks5_new(int client_fd) {
|
||||
struct socks5 * ret;
|
||||
|
||||
if (pool == NULL) {
|
||||
ret = malloc(sizeof(*ret));
|
||||
} else {
|
||||
ret = pool;
|
||||
pool = pool->next;
|
||||
ret->next = 0;
|
||||
}
|
||||
|
||||
if (ret == NULL) {
|
||||
goto finally;
|
||||
}
|
||||
memset(ret, 0x00, sizeof(*ret));
|
||||
|
||||
ret->origin_fd = -1;
|
||||
ret->client_fd = client_fd;
|
||||
ret->client_addr_len = sizeof(ret->client_addr);
|
||||
|
||||
ret->stm.initial = HELLO_READ;
|
||||
ret->stm.max_state = ERROR;
|
||||
ret->stm.states = socks5_describe_states();
|
||||
stm_init(&ret->stm);
|
||||
|
||||
buffer_init(&ret->read_buffer, N(ret->raw_buff_a), ret->raw_buff_a);
|
||||
buffer_init(&ret->write_buffer, N(ret->raw_buff_b), ret->raw_buff_b);
|
||||
|
||||
ret->references = 1;
|
||||
finally:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** realmente destruye */
|
||||
static void
|
||||
socks5_destroy_(struct socks5* s) {
|
||||
if(s->origin_resolution != NULL) {
|
||||
freeaddrinfo(s->origin_resolution);
|
||||
s->origin_resolution = 0;
|
||||
}
|
||||
free(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* destruye un `struct socks5', tiene en cuenta las referencias
|
||||
* y el pool de objetos.
|
||||
*/
|
||||
static void
|
||||
socks5_destroy(struct socks5 *s) {
|
||||
if (s != NULL) {
|
||||
if (s->references == 1) {
|
||||
if(pool_size < max_pool) {
|
||||
s->next = pool;
|
||||
pool = s;
|
||||
pool_size++;
|
||||
} else {
|
||||
socks5_destroy_(s);
|
||||
}
|
||||
} else {
|
||||
s->references -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
socksv5_pool_destroy(void) {
|
||||
struct socks5 *next, *s;
|
||||
for(s = pool; s != NULL ; s = next) {
|
||||
next = s->next;
|
||||
free(s);
|
||||
}
|
||||
}
|
||||
|
||||
/** obtiene el struct (socks5 *) desde la llave de selección */
|
||||
#define ATTACHMENT(key) ( (struct socks5 *)(key)->data)
|
||||
|
||||
/* declaración forward de los handlers de selección de una conexión
|
||||
* establecida entre un cliente y el proxy.
|
||||
*/
|
||||
static void socksv5_read (struct selector_key *key);
|
||||
static void socksv5_write (struct selector_key *key);
|
||||
static void socksv5_block (struct selector_key *key);
|
||||
static void socksv5_close (struct selector_key *key);
|
||||
static const struct fd_handler socks5_handler = {
|
||||
.handle_read = socksv5_read,
|
||||
.handle_write = socksv5_write,
|
||||
.handle_close = socksv5_close,
|
||||
.handle_block = socksv5_block,
|
||||
};
|
||||
|
||||
/** Intenta aceptar la nueva conexión entrante*/
|
||||
void
|
||||
socksv5_passive_accept(struct selector_key *key) {
|
||||
struct sockaddr_storage client_addr;
|
||||
socklen_t client_addr_len = sizeof(client_addr);
|
||||
struct socks5 *state = NULL;
|
||||
|
||||
const int client = accept(key->fd, (struct sockaddr*) &client_addr,
|
||||
&client_addr_len);
|
||||
if(client == -1) {
|
||||
goto fail;
|
||||
}
|
||||
if(selector_fd_set_nio(client) == -1) {
|
||||
goto fail;
|
||||
}
|
||||
state = socks5_new(client);
|
||||
if(state == NULL) {
|
||||
// sin un estado, nos es imposible manejaro.
|
||||
// tal vez deberiamos apagar accept() hasta que detectemos
|
||||
// que se liberó alguna conexión.
|
||||
goto fail;
|
||||
}
|
||||
memcpy(&state->client_addr, &client_addr, client_addr_len);
|
||||
state->client_addr_len = client_addr_len;
|
||||
|
||||
if(SELECTOR_SUCCESS != selector_register(key->s, client, &socks5_handler, OP_READ, state)) {
|
||||
goto fail;
|
||||
}
|
||||
return ;
|
||||
fail:
|
||||
if(client != -1) {
|
||||
close(client);
|
||||
}
|
||||
socks5_destroy(state);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// HELLO
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** callback del parser utilizado en `read_hello' */
|
||||
static void on_hello_method(struct hello_parser * p, const uint8_t method) {
|
||||
uint8_t *selected = p->data;
|
||||
|
||||
if (METHOD_NO_AUTHENTICATION_REQURED == method) {
|
||||
*selected = method;
|
||||
}
|
||||
}
|
||||
|
||||
/** inicializa las variables de los estados HELLO_… */
|
||||
static void hello_read_init(const unsigned state, struct selector_key * key) {
|
||||
struct hello_st *d = &ATTACHMENT(key)->client.hello;
|
||||
|
||||
d->rb = &(ATTACHMENT(key)->read_buffer);
|
||||
d->wb = &(ATTACHMENT(key)->write_buffer);
|
||||
d->parser.data = &d->method;
|
||||
d->parser.on_authentication_method = on_hello_method, hello_parser_init(
|
||||
&d->parser);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
hello_process(const struct hello_st* d);
|
||||
|
||||
/** lee todos los bytes del mensaje de tipo `hello' y inicia su proceso */
|
||||
static unsigned hello_read(struct selector_key * key) {
|
||||
struct hello_st *d = &ATTACHMENT(key)->client.hello;
|
||||
unsigned ret = HELLO_READ;
|
||||
bool error = false;
|
||||
uint8_t *ptr;
|
||||
size_t count;
|
||||
ssize_t n;
|
||||
|
||||
ptr = buffer_write_ptr(d->rb, &count);
|
||||
n = recv(key->fd, ptr, count, 0);
|
||||
if(n > 0) {
|
||||
buffer_write_adv(d->rb, n);
|
||||
const enum hello_state st = hello_consume(d->rb, &d->parser, &error);
|
||||
|
||||
if(hello_is_done(st, 0)) {
|
||||
if(SELECTOR_SUCCESS == selector_set_interest_key(key, OP_WRITE)) {
|
||||
ret = hello_process(d);
|
||||
} else {
|
||||
ret = ERROR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = ERROR;
|
||||
}
|
||||
|
||||
return error ? ERROR : ret;
|
||||
}
|
||||
|
||||
/** procesamiento del mensaje `hello' */
|
||||
static unsigned
|
||||
hello_process(const struct hello_st* d) {
|
||||
unsigned ret = HELLO_WRITE;
|
||||
|
||||
uint8_t m = d->method;
|
||||
const uint8_t r = (m == METHOD_NO_ACCEPTABLE_METHODS) ? 0xFF : 0x00;
|
||||
if (hello_marshall(d->wb, (enum socks_response_status) r) == -1) {
|
||||
ret = ERROR;
|
||||
}
|
||||
if (METHOD_NO_ACCEPTABLE_METHODS == m) {
|
||||
ret = ERROR;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hello_read_close(const unsigned state, struct selector_key * key) {
|
||||
struct hello_st * d = &ATTACHMENT(key)->client.hello;
|
||||
|
||||
hello_parser_close(&d->parser);
|
||||
}
|
||||
|
||||
static unsigned hello_write(struct selector_key * key) {
|
||||
struct hello_st * d = &ATTACHMENT(key)->client.hello;
|
||||
|
||||
unsigned ret = HELLO_WRITE;
|
||||
uint8_t * ptr;
|
||||
size_t count;
|
||||
ssize_t n;
|
||||
|
||||
ptr = buffer_read_ptr(d->wb, &count);
|
||||
n = send(key->fd, ptr, count, MSG_NOSIGNAL);
|
||||
if (n == -1) {
|
||||
ret = ERROR;
|
||||
} else {
|
||||
buffer_read_adv(d->wb, n);
|
||||
if (!buffer_can_read(d->wb)) {
|
||||
if (SELECTOR_SUCCESS == selector_set_interest_key(key, OP_READ)) {
|
||||
ret = REQUEST_READ;
|
||||
} else {
|
||||
ret = ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void request_init(const unsigned state, struct selector_key * key) {
|
||||
struct request_st * d = &ATTACHMENT(key)->client.request;
|
||||
|
||||
d->rb = &(ATTACHMENT(key)->read_buffer);
|
||||
d->wb = &(ATTACHMENT(key)->write_buffer);
|
||||
d->parser.request = &d->request;
|
||||
d->status = status_general_SOCKS_server_failure;
|
||||
request_parser_init(&d->parser);
|
||||
d->client_fd = &ATTACHMENT(key)->client_fd;
|
||||
d->origin_fd = &ATTACHMENT(key)->origin_fd;
|
||||
|
||||
d->origin_addr = &ATTACHMENT(key)->origin_addr;
|
||||
d->origin_addr_len = &ATTACHMENT(key)->origin_addr_len;
|
||||
d->origin_domain = &ATTACHMENT(key)->origin_domain;
|
||||
}
|
||||
|
||||
static unsigned request_process(struct selector_key * key, struct request_st * d);
|
||||
|
||||
static unsigned request_read(struct selector_key * key) {
|
||||
struct request_st * d = &ATTACHMENT(key)->client.request;
|
||||
|
||||
buffer *b = d->rb;
|
||||
unsigned ret = REQUEST_READ;
|
||||
bool error = false;
|
||||
uint8_t * ptr;
|
||||
size_t count;
|
||||
ssize_t n;
|
||||
|
||||
ptr = buffer_write_ptr(b, &count);
|
||||
n = recv(key->fd, ptr, count, 0);
|
||||
if (n > 0) {
|
||||
buffer_write_adv(b, n);
|
||||
int st = request_consume(b, &d->parser, &error);
|
||||
if (request_is_done(st, 0)) {
|
||||
ret = request_process(key, d);
|
||||
}
|
||||
} else {
|
||||
ret = ERROR;
|
||||
}
|
||||
|
||||
return error ? ERROR : ret;
|
||||
}
|
||||
|
||||
static unsigned request_connect(struct selector_key * key, struct request_st * d);
|
||||
static void * request_resolv_blocking(void * data);
|
||||
|
||||
static unsigned request_process(struct selector_key * key, struct request_st * d) {
|
||||
unsigned ret;
|
||||
pthread_t tid;
|
||||
|
||||
switch (d->request.cmd) {
|
||||
case socks_req_cmd_connect:
|
||||
switch (d->request.dest_addr_type) {
|
||||
case socks_req_addr_ipv4: {
|
||||
ATTACHMENT(key)->origin_domain = AF_INET;
|
||||
d->request.dest_addr.ipv4.sin_port = d->request.dest_port;
|
||||
ATTACHMENT(key)->origin_addr_len = sizeof(d->request.dest_addr.ipv4);
|
||||
// TODO: CAMBIAR, DA SUBDESBORDAMIENTO DE BÚFER :]
|
||||
memcpy(&ATTACHMENT(key)->origin_addr, &d->request.dest_addr, sizeof(d->request.dest_addr.ipv4));
|
||||
ret = request_connect(key, d);
|
||||
break;
|
||||
} case socks_req_addr_ipv6: {
|
||||
ATTACHMENT(key)->origin_domain = AF_INET6;
|
||||
d->request.dest_addr.ipv6.sin6_port = d->request.dest_port;
|
||||
ATTACHMENT(key)->origin_addr_len = sizeof(d->request.dest_addr.ipv6);
|
||||
// TODO: CAMBIAR, DA SUBDESBORDAMIENTO DE BÚFER :]
|
||||
memcpy(&ATTACHMENT(key)->origin_addr, &d->request.dest_addr, sizeof(d->request.dest_addr.ipv6));
|
||||
ret = request_connect(key, d);
|
||||
break;
|
||||
} case socks_req_addr_domain: {
|
||||
struct selector_key * k = malloc(sizeof(*key));
|
||||
if (k == NULL) {
|
||||
ret = REQUEST_WRITE;
|
||||
d->status = status_general_SOCKS_server_failure;
|
||||
selector_set_interest_key(key, OP_WRITE);
|
||||
} else {
|
||||
memcpy(k, key, sizeof(*k));
|
||||
if (-1 == pthread_create(&tid, 0, request_resolv_blocking, k)) {
|
||||
ret = REQUEST_WRITE;
|
||||
d->status = status_general_SOCKS_server_failure;
|
||||
selector_set_interest_key(key, OP_WRITE);
|
||||
} else {
|
||||
ret = REQUEST_RESOLV;
|
||||
selector_set_interest_key(key, OP_NOOP);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} default: {
|
||||
ret = REQUEST_WRITE;
|
||||
d->status = status_address_type_not_supported;
|
||||
selector_set_interest_key(key, OP_WRITE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case socks_req_cmd_bind:
|
||||
case socks_req_cmd_associate:
|
||||
default:
|
||||
d->status = status_command_not_supported;
|
||||
ret = REQUEST_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void * request_resolv_blocking(void * data) {
|
||||
struct selector_key * key = (struct selector_key *) data;
|
||||
struct socks5 * s = ATTACHMENT(key);
|
||||
|
||||
pthread_detach(pthread_self());
|
||||
s->origin_resolution = 0;
|
||||
struct addrinfo hints = {
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
.ai_flags = AI_PASSIVE,
|
||||
.ai_protocol = 0,
|
||||
.ai_canonname = NULL,
|
||||
.ai_addr = NULL,
|
||||
.ai_next = NULL,
|
||||
};
|
||||
|
||||
char buff[7];
|
||||
snprintf(buff, sizeof(buff), "%d", ntohs(s->client.request.request.dest_port));
|
||||
|
||||
getaddrinfo(s->client.request.request.dest_addr.fqdn, buff, &hints, &s->origin_resolution);
|
||||
|
||||
// TODO:manejar el error de getaddrinfo
|
||||
|
||||
selector_notify_block(key->s, key->fd);
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned request_resolv_done(struct selector_key * key) {
|
||||
struct request_st * d = &ATTACHMENT(key)->client.request;
|
||||
struct socks5 * s = ATTACHMENT(key);
|
||||
|
||||
if (s->origin_resolution == 0) {
|
||||
d->status = status_general_SOCKS_server_failure;
|
||||
} else {
|
||||
s->origin_domain = s->origin_resolution->ai_family;
|
||||
s->origin_addr_len = s->origin_resolution->ai_addrlen;
|
||||
memcpy(&s->origin_addr, s->origin_resolution->ai_addr, s->origin_resolution->ai_addrlen);
|
||||
freeaddrinfo(s->origin_resolution);
|
||||
s->origin_resolution = 0;
|
||||
}
|
||||
|
||||
return request_connect(key, d);
|
||||
}
|
||||
|
||||
static unsigned request_connect(struct selector_key * key, struct request_st * d) {
|
||||
bool error = false;
|
||||
enum socks_response_status status = d->status;
|
||||
int *fd = d->origin_fd;
|
||||
|
||||
*fd = socket(ATTACHMENT(key)->origin_domain, SOCK_STREAM, 0);
|
||||
if (*fd == -1) {
|
||||
error = true;
|
||||
goto finally;
|
||||
}
|
||||
if (selector_fd_set_nio(*fd) == -1) {
|
||||
goto finally;
|
||||
}
|
||||
|
||||
if (-1 == connect(*fd, (const struct sockaddr *) &ATTACHMENT(key)->origin_addr, ATTACHMENT(key)->origin_addr_len)) {
|
||||
if (errno == EINPROGRESS) {
|
||||
selector_status st = selector_set_interest_key(key, OP_NOOP);
|
||||
if (SELECTOR_SUCCESS != st) {
|
||||
error = true;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
st = selector_register(key->s, *fd, &socks5_handler, OP_WRITE, key->data);
|
||||
if (SELECTOR_SUCCESS != st) {
|
||||
error = true;
|
||||
goto finally;
|
||||
}
|
||||
ATTACHMENT(key)->references += 1;
|
||||
} else {
|
||||
status = errno_to_socks(errno);
|
||||
error = true;
|
||||
goto finally;
|
||||
}
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
finally:
|
||||
if (error) {
|
||||
if (*fd != -1) {
|
||||
close(*fd);
|
||||
*fd = -1;
|
||||
}
|
||||
}
|
||||
d->status = status;
|
||||
|
||||
return REQUEST_CONNECTING;
|
||||
}
|
||||
|
||||
static void request_read_close(const unsigned state, struct selector_key * key) {
|
||||
struct request_st * d = &ATTACHMENT(key)->client.request;
|
||||
|
||||
request_close(&d->parser);
|
||||
}
|
||||
|
||||
static void request_connecting_init(const unsigned state, struct selector_key *key){
|
||||
struct connecting * d = &ATTACHMENT(key)->orig.conn;
|
||||
|
||||
d->client_fd = &ATTACHMENT(key)->client_fd;
|
||||
d->origin_fd = &ATTACHMENT(key)->origin_fd;
|
||||
d->status = &ATTACHMENT(key)->client.request.status;
|
||||
d->wb = &ATTACHMENT(key)->write_buffer;
|
||||
}
|
||||
|
||||
static unsigned request_connecting(struct selector_key * key) {
|
||||
int error;
|
||||
socklen_t len = sizeof(error);
|
||||
struct connecting * d = &ATTACHMENT(key)->orig.conn;
|
||||
|
||||
if (getsockopt(key->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
|
||||
*d->status = status_general_SOCKS_server_failure;
|
||||
} else {
|
||||
if (error == 0) {
|
||||
*d->status = status_succeeded;
|
||||
*d->origin_fd = key->fd;
|
||||
} else {
|
||||
*d->status = errno_to_socks(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == request_marshall(d->wb, *d->status)) {
|
||||
*d->status = status_general_SOCKS_server_failure;
|
||||
abort();
|
||||
}
|
||||
selector_status s = 0;
|
||||
s |= selector_set_interest(key->s, *d->client_fd, OP_WRITE);
|
||||
s |= selector_set_interest_key(key, OP_NOOP);
|
||||
return SELECTOR_SUCCESS == s ? REQUEST_WRITE : ERROR;
|
||||
}
|
||||
|
||||
// void log_request(enum socks_response_status status, const struct sockaddr * clientaddr, const struct sockaddr * originaddr);
|
||||
|
||||
static unsigned request_write(struct selector_key * key) {
|
||||
struct request_st * d = &ATTACHMENT(key)->client.request;
|
||||
|
||||
unsigned ret = REQUEST_WRITE;
|
||||
buffer * b = d->wb;
|
||||
uint8_t * ptr;
|
||||
size_t count;
|
||||
ssize_t n;
|
||||
|
||||
ptr = buffer_read_ptr(b, &count);
|
||||
n = send(key->fd, ptr, count, MSG_NOSIGNAL);
|
||||
if (-1 == n){
|
||||
ret = ERROR;
|
||||
} else {
|
||||
buffer_read_adv(b, n);
|
||||
|
||||
if (!buffer_can_read(b)){
|
||||
if (d->status == status_succeeded) {
|
||||
ret = COPY;
|
||||
selector_set_interest(key->s, *d->client_fd, OP_READ);
|
||||
selector_set_interest(key->s, *d->origin_fd, OP_READ);
|
||||
} else {
|
||||
ret = DONE;
|
||||
selector_set_interest(key->s, *d->client_fd, OP_NOOP);
|
||||
if (-1 == *d->origin_fd) {
|
||||
selector_set_interest(key->s, *d->origin_fd, OP_NOOP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log_request(d->status, (const struct sockaddr *) &ATTACHMENT(key)->client_addr, (const struct sockaddr *) &ATTACHMENT(key)->origin_addr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void copy_init(const unsigned state, struct selector_key * key) {
|
||||
struct copy * d = &ATTACHMENT(key)->client.copy;
|
||||
|
||||
d->fd = &ATTACHMENT(key)->client_fd;
|
||||
d->rb = &ATTACHMENT(key)->read_buffer;
|
||||
d->wb = &ATTACHMENT(key)->write_buffer;
|
||||
d->duplex = OP_READ | OP_WRITE;
|
||||
d->other = &ATTACHMENT(key)->orig.copy;
|
||||
|
||||
d = &ATTACHMENT(key)->orig.copy;
|
||||
d->fd = &ATTACHMENT(key)->origin_fd;
|
||||
d->rb = &ATTACHMENT(key)->write_buffer;
|
||||
d->wb = &ATTACHMENT(key)->read_buffer;
|
||||
d->duplex = OP_READ | OP_WRITE;
|
||||
d->other = &ATTACHMENT(key)->client.copy;
|
||||
}
|
||||
|
||||
static unsigned copy_compute_interests(fd_selector s, struct copy * d) {
|
||||
// ... TODO ...
|
||||
|
||||
fd_interest ret = OP_NOOP;
|
||||
if ((d->duplex & OP_READ) && buffer_can_write(d->rb)) {
|
||||
ret |= OP_READ;
|
||||
}
|
||||
if ((d->duplex & OP_WRITE) && buffer_can_read(d->wb)) {
|
||||
ret |= OP_WRITE;
|
||||
}
|
||||
|
||||
if (SELECTOR_SUCCESS != selector_set_interest(s, *d->fd, ret)) {
|
||||
abort();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct copy * copy_ptr(struct selector_key * key) {
|
||||
struct copy * d = &ATTACHMENT(key)->client.copy;
|
||||
if (*d->fd != key->fd) {
|
||||
d = d->other;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static unsigned copy_r(struct selector_key * key) {
|
||||
struct copy *d = copy_ptr(key);
|
||||
|
||||
assert(*d->fd == key->fd);
|
||||
|
||||
size_t size;
|
||||
ssize_t n;
|
||||
buffer * b = d->rb;
|
||||
unsigned ret = COPY;
|
||||
|
||||
uint8_t * ptr = buffer_write_ptr(b, &size);
|
||||
n = recv(key->fd, ptr, size, 0);
|
||||
if (n <= 0) {
|
||||
shutdown(*d->fd, SHUT_RD);
|
||||
d->duplex &= ~OP_READ;
|
||||
if (*d->other->fd != -1) {
|
||||
shutdown(*d->other->fd, SHUT_WR);
|
||||
d->other->duplex &= ~OP_WRITE;
|
||||
}
|
||||
} else {
|
||||
buffer_write_adv(b, n);
|
||||
}
|
||||
copy_compute_interests(key->s, d);
|
||||
copy_compute_interests(key->s, d->other);
|
||||
if (d->duplex == OP_NOOP) {
|
||||
ret = DONE;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned copy_w(struct selector_key * key) {
|
||||
struct copy * d = copy_ptr(key);
|
||||
|
||||
assert(*d->fd == key->fd);
|
||||
size_t size;
|
||||
ssize_t n;
|
||||
buffer * b = d->wb;
|
||||
unsigned ret = COPY;
|
||||
|
||||
uint8_t * ptr = buffer_read_ptr(b, &size);
|
||||
n = send(key->fd, ptr, size, MSG_NOSIGNAL);
|
||||
if (n == -1) {
|
||||
shutdown(*d->fd, SHUT_WR);
|
||||
d->duplex &= ~OP_WRITE;
|
||||
if (*d->other->fd != -1) {
|
||||
shutdown(*d->other->fd, SHUT_RD);
|
||||
d->other->duplex &= ~OP_READ;
|
||||
}
|
||||
} else {
|
||||
buffer_read_adv(b, n);
|
||||
}
|
||||
copy_compute_interests(key->s, d);
|
||||
copy_compute_interests(key->s, d->other);
|
||||
if (d->duplex == OP_NOOP) {
|
||||
ret = DONE;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** definición de handlers para cada estado */
|
||||
static const struct state_definition client_statbl[] = {
|
||||
{
|
||||
.state = HELLO_READ,
|
||||
.on_arrival = hello_read_init,
|
||||
.on_departure = hello_read_close,
|
||||
.on_read_ready = hello_read,
|
||||
}, {
|
||||
.state = HELLO_WRITE,
|
||||
.on_write_ready = hello_write,
|
||||
}, {
|
||||
.state = REQUEST_READ,
|
||||
.on_arrival = request_init,
|
||||
.on_departure = request_read_close,
|
||||
.on_read_ready = request_read,
|
||||
}, {
|
||||
.state = REQUEST_RESOLV,
|
||||
.on_block_ready = request_resolv_done,
|
||||
}, {
|
||||
.state = REQUEST_CONNECTING,
|
||||
.on_arrival = request_connecting_init,
|
||||
.on_write_ready = request_connecting,
|
||||
}, {
|
||||
.state = REQUEST_WRITE,
|
||||
.on_write_ready = request_write,
|
||||
}, {
|
||||
.state = COPY,
|
||||
.on_arrival = copy_init,
|
||||
.on_read_ready = copy_r,
|
||||
.on_write_ready = copy_w,
|
||||
}, {
|
||||
.state = DONE,
|
||||
}, {
|
||||
.state = ERROR,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct state_definition * socks5_describe_states(void) {
|
||||
return client_statbl;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Handlers top level de la conexión pasiva.
|
||||
// son los que emiten los eventos a la maquina de estados.
|
||||
static void
|
||||
socksv5_done(struct selector_key* key);
|
||||
|
||||
static void
|
||||
socksv5_read(struct selector_key *key) {
|
||||
struct state_machine *stm = &ATTACHMENT(key)->stm;
|
||||
const enum socks_v5state st = stm_handler_read(stm, key);
|
||||
|
||||
if(ERROR == st || DONE == st) {
|
||||
socksv5_done(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socksv5_write(struct selector_key *key) {
|
||||
struct state_machine *stm = &ATTACHMENT(key)->stm;
|
||||
const enum socks_v5state st = stm_handler_write(stm, key);
|
||||
|
||||
if(ERROR == st || DONE == st) {
|
||||
socksv5_done(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socksv5_block(struct selector_key *key) {
|
||||
struct state_machine *stm = &ATTACHMENT(key)->stm;
|
||||
const enum socks_v5state st = stm_handler_block(stm, key);
|
||||
|
||||
if(ERROR == st || DONE == st) {
|
||||
socksv5_done(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socksv5_close(struct selector_key *key) {
|
||||
socks5_destroy(ATTACHMENT(key));
|
||||
}
|
||||
|
||||
static void
|
||||
socksv5_done(struct selector_key* key) {
|
||||
const int fds[] = {
|
||||
ATTACHMENT(key)->client_fd,
|
||||
ATTACHMENT(key)->origin_fd,
|
||||
};
|
||||
for(unsigned i = 0; i < N(fds); i++) {
|
||||
if(fds[i] != -1) {
|
||||
if(SELECTOR_SUCCESS != selector_unregister_fd(key->s, fds[i])) {
|
||||
abort();
|
||||
}
|
||||
close(fds[i]);
|
||||
}
|
||||
}
|
||||
}
|
32
src/stm.c
32
src/stm.c
|
@ -1,7 +1,5 @@
|
|||
/**
|
||||
* stm.c - pequeño motor de maquina de estados donde los eventos son los
|
||||
* del selector.c
|
||||
*/
|
||||
// This is a personal academic project. Dear PVS-Studio, please check it.
|
||||
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
#include <stdlib.h>
|
||||
#include "stm.h"
|
||||
|
||||
|
@ -11,12 +9,12 @@ void
|
|||
stm_init(struct state_machine *stm) {
|
||||
// verificamos que los estados son correlativos, y que están bien asignados.
|
||||
for(unsigned i = 0 ; i <= stm->max_state; i++) {
|
||||
if(i != stm->states[i].state) {
|
||||
if (i != stm->states[i].state) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
if(stm->initial < stm->max_state) {
|
||||
if (stm->initial < stm->max_state) {
|
||||
stm->current = NULL;
|
||||
} else {
|
||||
abort();
|
||||
|
@ -25,9 +23,9 @@ stm_init(struct state_machine *stm) {
|
|||
|
||||
inline static void
|
||||
handle_first(struct state_machine *stm, struct selector_key *key) {
|
||||
if(stm->current == NULL) {
|
||||
if (stm->current == NULL) {
|
||||
stm->current = stm->states + stm->initial;
|
||||
if(NULL != stm->current->on_arrival) {
|
||||
if (NULL != stm->current->on_arrival) {
|
||||
stm->current->on_arrival(stm->current->state, key);
|
||||
}
|
||||
}
|
||||
|
@ -35,16 +33,16 @@ handle_first(struct state_machine *stm, struct selector_key *key) {
|
|||
|
||||
inline static
|
||||
void jump(struct state_machine *stm, unsigned next, struct selector_key *key) {
|
||||
if(next > stm->max_state) {
|
||||
if (next > stm->max_state) {
|
||||
abort();
|
||||
}
|
||||
if(stm->current != stm->states + next) {
|
||||
if(stm->current != NULL && stm->current->on_departure != NULL) {
|
||||
if (stm->current != stm->states + next) {
|
||||
if (stm->current != NULL && stm->current->on_departure != NULL) {
|
||||
stm->current->on_departure(stm->current->state, key);
|
||||
}
|
||||
stm->current = stm->states + next;
|
||||
|
||||
if(NULL != stm->current->on_arrival) {
|
||||
if (NULL != stm->current->on_arrival) {
|
||||
stm->current->on_arrival(stm->current->state, key);
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +51,7 @@ void jump(struct state_machine *stm, unsigned next, struct selector_key *key) {
|
|||
unsigned
|
||||
stm_handler_read(struct state_machine *stm, struct selector_key *key) {
|
||||
handle_first(stm, key);
|
||||
if(stm->current->on_read_ready == 0) {
|
||||
if (stm->current->on_read_ready == 0) {
|
||||
abort();
|
||||
}
|
||||
const unsigned int ret = stm->current->on_read_ready(key);
|
||||
|
@ -65,7 +63,7 @@ stm_handler_read(struct state_machine *stm, struct selector_key *key) {
|
|||
unsigned
|
||||
stm_handler_write(struct state_machine *stm, struct selector_key *key) {
|
||||
handle_first(stm, key);
|
||||
if(stm->current->on_write_ready == 0) {
|
||||
if (stm->current->on_write_ready == 0) {
|
||||
abort();
|
||||
}
|
||||
const unsigned int ret = stm->current->on_write_ready(key);
|
||||
|
@ -77,7 +75,7 @@ stm_handler_write(struct state_machine *stm, struct selector_key *key) {
|
|||
unsigned
|
||||
stm_handler_block(struct state_machine *stm, struct selector_key *key) {
|
||||
handle_first(stm, key);
|
||||
if(stm->current->on_block_ready == 0) {
|
||||
if (stm->current->on_block_ready == 0) {
|
||||
abort();
|
||||
}
|
||||
const unsigned int ret = stm->current->on_block_ready(key);
|
||||
|
@ -88,7 +86,7 @@ stm_handler_block(struct state_machine *stm, struct selector_key *key) {
|
|||
|
||||
void
|
||||
stm_handler_close(struct state_machine *stm, struct selector_key *key) {
|
||||
if(stm->current != NULL && stm->current->on_departure != NULL) {
|
||||
if (stm->current != NULL && stm->current->on_departure != NULL) {
|
||||
stm->current->on_departure(stm->current->state, key);
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +94,7 @@ stm_handler_close(struct state_machine *stm, struct selector_key *key) {
|
|||
unsigned
|
||||
stm_state(struct state_machine *stm) {
|
||||
unsigned ret = stm->initial;
|
||||
if(stm->current != NULL) {
|
||||
if (stm->current != NULL) {
|
||||
ret= stm->current->state;
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <check.h>
|
||||
|
||||
// asi se puede probar las funciones internas
|
||||
#include "buffer.c"
|
||||
|
||||
#define N(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
|
||||
START_TEST (test_buffer_misc) {
|
||||
struct buffer buf;
|
||||
buffer *b = &buf;
|
||||
uint8_t direct_buff[6];
|
||||
buffer_init(&buf, N(direct_buff), direct_buff);
|
||||
ck_assert_ptr_eq(&buf, b);
|
||||
|
||||
ck_assert_int_eq(true, buffer_can_write(b));
|
||||
ck_assert_int_eq(false, buffer_can_read(b));
|
||||
|
||||
size_t wbytes = 0, rbytes = 0;
|
||||
uint8_t *ptr = buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(6, wbytes);
|
||||
// escribo 4 bytes
|
||||
uint8_t first_write [] = {
|
||||
'H', 'O', 'L', 'A',
|
||||
};
|
||||
memcpy(ptr, first_write, sizeof(first_write));
|
||||
buffer_write_adv(b, sizeof(first_write));
|
||||
|
||||
// quedan 2 libres para escribir
|
||||
buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(2, wbytes);
|
||||
|
||||
// tengo por leer
|
||||
buffer_read_ptr(b, &rbytes);
|
||||
ck_assert_uint_eq(4, rbytes);
|
||||
|
||||
// leo 3 del buffer
|
||||
ck_assert_uint_eq('H', buffer_read(b));
|
||||
ck_assert_uint_eq('O', buffer_read(b));
|
||||
ck_assert_uint_eq('L', buffer_read(b));
|
||||
|
||||
// queda 1 por leer
|
||||
buffer_read_ptr(b, &rbytes);
|
||||
ck_assert_uint_eq(1, rbytes);
|
||||
|
||||
// quiero escribir..tendria que seguir habiendo 2 libres
|
||||
ptr = buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(2, wbytes);
|
||||
|
||||
uint8_t second_write [] = {
|
||||
' ', 'M',
|
||||
};
|
||||
memcpy(ptr, second_write, sizeof(second_write));
|
||||
buffer_write_adv(b, sizeof(second_write));
|
||||
|
||||
ck_assert_int_eq(false, buffer_can_write(b));
|
||||
buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(0, wbytes);
|
||||
|
||||
// tiene que haber 2 + 1 para leer
|
||||
ptr = buffer_read_ptr(b, &rbytes);
|
||||
ck_assert_uint_eq(3, rbytes);
|
||||
ck_assert_ptr_ne(ptr, b->data);
|
||||
|
||||
buffer_compact(b);
|
||||
ck_assert_ptr_eq(b->data, buffer_read_ptr(b, &rbytes));
|
||||
ck_assert_uint_eq(3, rbytes);
|
||||
ck_assert_ptr_eq(b->data + 3, buffer_write_ptr(b, &wbytes));
|
||||
ck_assert_uint_eq(3, wbytes);
|
||||
|
||||
uint8_t third_write [] = {
|
||||
'U', 'N', 'D',
|
||||
};
|
||||
memcpy(ptr, third_write, sizeof(third_write));
|
||||
buffer_write_adv(b, sizeof(third_write));
|
||||
|
||||
buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(0, wbytes);
|
||||
ck_assert_ptr_eq(b->data, buffer_read_ptr(b, &rbytes));
|
||||
buffer_read_adv(b, rbytes);
|
||||
buffer_read_ptr(b, &rbytes);
|
||||
ck_assert_uint_eq(0, rbytes);
|
||||
ck_assert_ptr_eq(b->data, buffer_write_ptr(b, &wbytes));
|
||||
ck_assert_uint_eq(6, wbytes);
|
||||
|
||||
buffer_compact(b);
|
||||
buffer_read_ptr(b, &rbytes);
|
||||
ck_assert_uint_eq(0, rbytes);
|
||||
buffer_write_ptr(b, &wbytes);
|
||||
ck_assert_uint_eq(N(direct_buff), wbytes);
|
||||
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
suite(void) {
|
||||
Suite *s = suite_create("buffer");
|
||||
TCase *tc = tcase_create("buffer");
|
||||
|
||||
tcase_add_test(tc, test_buffer_misc);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
main(void) {
|
||||
SRunner *sr = srunner_create(suite());
|
||||
int number_failed;
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <check.h>
|
||||
|
||||
#include "netutils.h"
|
||||
|
||||
START_TEST (test_sockaddr_to_human_ipv4) {
|
||||
char buff[50] = {0};
|
||||
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(9090),
|
||||
};
|
||||
addr.sin_addr.s_addr = htonl(0x01020304);
|
||||
const struct sockaddr *x = (const struct sockaddr *) &addr;
|
||||
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, sizeof(buff)/sizeof(buff[0]), x),
|
||||
"1.2.3.4:9090");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 5, x), "unkn");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 8, x), "1.2.3.4");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 9, x), "1.2.3.4:");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 10, x), "1.2.3.4:9");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 11, x), "1.2.3.4:90");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 12, x), "1.2.3.4:909");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 13, x), "1.2.3.4:9090");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
||||
START_TEST (test_sockaddr_to_human_ipv6) {
|
||||
char buff[50] = {0};
|
||||
|
||||
struct sockaddr_in6 addr = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_port = htons(9090),
|
||||
};
|
||||
uint8_t *d = ((uint8_t *)&addr.sin6_addr);
|
||||
for(int i = 0; i < 16; i++) {
|
||||
d[i] = 0xFF;
|
||||
}
|
||||
|
||||
const struct sockaddr *x = (const struct sockaddr *) &addr;
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 10, x), "unknown i");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 39, x), "unknown ip:9090");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 40, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 41, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 42, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:9");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 43, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:90");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 44, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:909");
|
||||
ck_assert_str_eq(sockaddr_to_human(buff, 45, x),
|
||||
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:9090");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
hello_suite(void) {
|
||||
Suite *s;
|
||||
TCase *tc;
|
||||
|
||||
s = suite_create("socks");
|
||||
|
||||
/* Core test case */
|
||||
tc = tcase_create("netutils");
|
||||
|
||||
tcase_add_test(tc, test_sockaddr_to_human_ipv4);
|
||||
tcase_add_test(tc, test_sockaddr_to_human_ipv6);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = hello_suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <check.h>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
// definición de maquina
|
||||
|
||||
enum states {
|
||||
S0,
|
||||
S1
|
||||
};
|
||||
|
||||
enum event_type {
|
||||
FOO,
|
||||
BAR,
|
||||
};
|
||||
|
||||
static void
|
||||
foo(struct parser_event *ret, const uint8_t c) {
|
||||
ret->type = FOO;
|
||||
ret->n = 1;
|
||||
ret->data[0] = c;
|
||||
}
|
||||
|
||||
static void
|
||||
bar(struct parser_event *ret, const uint8_t c) {
|
||||
ret->type = BAR;
|
||||
ret->n = 1;
|
||||
ret->data[0] = c;
|
||||
}
|
||||
|
||||
static const struct parser_state_transition ST_S0 [] = {
|
||||
{.when = 'F', .dest = S0, .act1 = foo,},
|
||||
{.when = 'f', .dest = S0, .act1 = foo,},
|
||||
{.when = ANY, .dest = S1, .act1 = bar,},
|
||||
};
|
||||
static const struct parser_state_transition ST_S1 [] = {
|
||||
{.when = 'F', .dest = S0, .act1 = foo,},
|
||||
{.when = 'f', .dest = S0, .act1 = foo,},
|
||||
{.when = ANY, .dest = S1, .act1 = bar,},
|
||||
};
|
||||
|
||||
static const struct parser_state_transition *states [] = {
|
||||
ST_S0,
|
||||
ST_S1,
|
||||
};
|
||||
|
||||
#define N(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
static const size_t states_n [] = {
|
||||
N(ST_S0),
|
||||
N(ST_S1),
|
||||
};
|
||||
|
||||
static struct parser_definition definition = {
|
||||
.states_count = N(states),
|
||||
.states = states,
|
||||
.states_n = states_n,
|
||||
.start_state = S0,
|
||||
};
|
||||
|
||||
//// TEST
|
||||
|
||||
static void
|
||||
assert_eq(const unsigned type, const int c, const struct parser_event *e) {
|
||||
ck_assert_ptr_eq (0, e->next);
|
||||
ck_assert_uint_eq(1, e->n);
|
||||
ck_assert_uint_eq(type, e->type);
|
||||
ck_assert_uint_eq(c, e->data[0]);
|
||||
|
||||
}
|
||||
|
||||
START_TEST (test_basic) {
|
||||
struct parser *parser = parser_init(parser_no_classes(), &definition);
|
||||
assert_eq(FOO, 'f', parser_feed(parser, 'f'));
|
||||
assert_eq(FOO, 'F', parser_feed(parser, 'F'));
|
||||
assert_eq(BAR, 'B', parser_feed(parser, 'B'));
|
||||
assert_eq(BAR, 'b', parser_feed(parser, 'b'));
|
||||
|
||||
parser_destroy(parser);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
suite(void) {
|
||||
Suite *s;
|
||||
TCase *tc;
|
||||
|
||||
s = suite_create("parser_utils");
|
||||
|
||||
/* Core test case */
|
||||
tc = tcase_create("parser_utils");
|
||||
|
||||
tcase_add_test(tc, test_basic);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <check.h>
|
||||
|
||||
#include "parser_utils.h"
|
||||
|
||||
static void
|
||||
assert_eq(const unsigned type, const int c, const struct parser_event *e) {
|
||||
ck_assert_ptr_eq (0, e->next);
|
||||
ck_assert_uint_eq(1, e->n);
|
||||
ck_assert_uint_eq(type, e->type);
|
||||
ck_assert_uint_eq(c, e->data[0]);
|
||||
|
||||
}
|
||||
|
||||
START_TEST (test_eq) {
|
||||
const struct parser_definition d = parser_utils_strcmpi("foo");
|
||||
|
||||
struct parser *parser = parser_init(parser_no_classes(), &d);
|
||||
assert_eq(STRING_CMP_MAYEQ, 'f', parser_feed(parser, 'f'));
|
||||
assert_eq(STRING_CMP_MAYEQ, 'O', parser_feed(parser, 'O'));
|
||||
assert_eq(STRING_CMP_EQ, 'o', parser_feed(parser, 'o'));
|
||||
assert_eq(STRING_CMP_NEQ, 'X', parser_feed(parser, 'X'));
|
||||
assert_eq(STRING_CMP_NEQ, 'y', parser_feed(parser, 'y'));
|
||||
|
||||
parser_destroy(parser);
|
||||
parser_utils_strcmpi_destroy(&d);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
suite(void) {
|
||||
Suite *s;
|
||||
TCase *tc;
|
||||
|
||||
s = suite_create("parser_utils");
|
||||
|
||||
/* Core test case */
|
||||
tc = tcase_create("parser_utils");
|
||||
|
||||
tcase_add_test(tc, test_eq);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <check.h>
|
||||
|
||||
#define INITIAL_SIZE ((size_t) 1024)
|
||||
|
||||
// para poder testear las funciones estaticas
|
||||
#include "selector.c"
|
||||
|
||||
START_TEST (test_selector_error) {
|
||||
const selector_status data[] = {
|
||||
SELECTOR_SUCCESS,
|
||||
SELECTOR_ENOMEM,
|
||||
SELECTOR_MAXFD,
|
||||
SELECTOR_IARGS,
|
||||
SELECTOR_IO,
|
||||
};
|
||||
// verifica que `selector_error' tiene mensajes especificos
|
||||
for(unsigned i = 0 ; i < N(data); i++) {
|
||||
ck_assert_str_ne(ERROR_DEFAULT_MSG, selector_error(data[i]));
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST (test_next_capacity) {
|
||||
const size_t data[] = {
|
||||
0, 1,
|
||||
1, 2,
|
||||
2, 4,
|
||||
3, 4,
|
||||
4, 8,
|
||||
7, 8,
|
||||
8, 16,
|
||||
15, 16,
|
||||
31, 32,
|
||||
16, 32,
|
||||
ITEMS_MAX_SIZE, ITEMS_MAX_SIZE,
|
||||
ITEMS_MAX_SIZE + 1, ITEMS_MAX_SIZE,
|
||||
};
|
||||
for(unsigned i = 0; i < N(data) / 2; i++ ) {
|
||||
ck_assert_uint_eq(data[i * 2 + 1] + 1, next_capacity(data[i*2]));
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST (test_ensure_capacity) {
|
||||
fd_selector s = selector_new(0);
|
||||
for(size_t i = 0; i < s->fd_size; i++) {
|
||||
ck_assert_int_eq(FD_UNUSED, s->fds[i].fd);
|
||||
}
|
||||
|
||||
size_t n = 1;
|
||||
ck_assert_int_eq(SELECTOR_SUCCESS, ensure_capacity(s, n));
|
||||
ck_assert_uint_ge(s->fd_size, n);
|
||||
|
||||
n = 10;
|
||||
ck_assert_int_eq(SELECTOR_SUCCESS, ensure_capacity(s, n));
|
||||
ck_assert_uint_ge(s->fd_size, n);
|
||||
|
||||
const size_t last_size = s->fd_size;
|
||||
n = ITEMS_MAX_SIZE + 1;
|
||||
ck_assert_int_eq(SELECTOR_MAXFD, ensure_capacity(s, n));
|
||||
ck_assert_uint_eq(last_size, s->fd_size);
|
||||
|
||||
for(size_t i = 0; i < s->fd_size; i++) {
|
||||
ck_assert_int_eq(FD_UNUSED, s->fds[i].fd);
|
||||
}
|
||||
|
||||
selector_destroy(s);
|
||||
|
||||
ck_assert_ptr_null(selector_new(ITEMS_MAX_SIZE + 1));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
// callbacks de prueba
|
||||
static void *data_mark = (void *)0x0FF1CE;
|
||||
static unsigned destroy_count = 0;
|
||||
static void
|
||||
destroy_callback(struct selector_key *key) {
|
||||
ck_assert_ptr_nonnull(key->s);
|
||||
ck_assert_int_ge(key->fd, 0);
|
||||
ck_assert_int_lt(key->fd, ITEMS_MAX_SIZE);
|
||||
|
||||
ck_assert_ptr_eq(data_mark, key->data);
|
||||
destroy_count++;
|
||||
}
|
||||
|
||||
START_TEST (test_selector_register_fd) {
|
||||
destroy_count = 0;
|
||||
fd_selector s = selector_new(INITIAL_SIZE);
|
||||
ck_assert_ptr_nonnull(s);
|
||||
|
||||
ck_assert_uint_eq(SELECTOR_IARGS, selector_register(0, -1, 0, 0, data_mark));
|
||||
|
||||
const struct fd_handler h = {
|
||||
.handle_read = NULL,
|
||||
.handle_write = NULL,
|
||||
.handle_close = destroy_callback,
|
||||
};
|
||||
int fd = ITEMS_MAX_SIZE - 1;
|
||||
ck_assert_uint_eq(SELECTOR_SUCCESS,
|
||||
selector_register(s, fd, &h, 0, data_mark));
|
||||
const struct item *item = s->fds + fd;
|
||||
ck_assert_int_eq (fd, s->max_fd);
|
||||
ck_assert_int_eq (fd, item->fd);
|
||||
ck_assert_ptr_eq (&h, item->handler);
|
||||
ck_assert_uint_eq(0, item->interest);
|
||||
ck_assert_ptr_eq (data_mark, item->data);
|
||||
|
||||
selector_destroy(s);
|
||||
// destroy desregistró?
|
||||
ck_assert_uint_eq(1, destroy_count);
|
||||
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST (test_selector_register_unregister_register) {
|
||||
destroy_count = 0;
|
||||
fd_selector s = selector_new(INITIAL_SIZE);
|
||||
ck_assert_ptr_nonnull(s);
|
||||
|
||||
const struct fd_handler h = {
|
||||
.handle_read = NULL,
|
||||
.handle_write = NULL,
|
||||
.handle_close = destroy_callback,
|
||||
};
|
||||
int fd = ITEMS_MAX_SIZE - 1;
|
||||
ck_assert_uint_eq(SELECTOR_SUCCESS,
|
||||
selector_register(s, fd, &h, 0, data_mark));
|
||||
ck_assert_uint_eq(SELECTOR_SUCCESS,
|
||||
selector_unregister_fd(s, fd));
|
||||
|
||||
const struct item *item = s->fds + fd;
|
||||
ck_assert_int_eq (0, s->max_fd);
|
||||
ck_assert_int_eq (FD_UNUSED, item->fd);
|
||||
ck_assert_ptr_eq (0x00, item->handler);
|
||||
ck_assert_uint_eq(0, item->interest);
|
||||
ck_assert_ptr_eq (0x00, item->data);
|
||||
|
||||
ck_assert_uint_eq(SELECTOR_SUCCESS,
|
||||
selector_register(s, fd, &h, 0, data_mark));
|
||||
item = s->fds + fd;
|
||||
ck_assert_int_eq (fd, s->max_fd);
|
||||
ck_assert_int_eq (fd, item->fd);
|
||||
ck_assert_ptr_eq (&h, item->handler);
|
||||
ck_assert_uint_eq(0, item->interest);
|
||||
ck_assert_ptr_eq (data_mark, item->data);
|
||||
|
||||
selector_destroy(s);
|
||||
ck_assert_uint_eq(2, destroy_count);
|
||||
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
suite(void) {
|
||||
Suite *s = suite_create("nio");
|
||||
TCase *tc = tcase_create("nio");
|
||||
|
||||
tcase_add_test(tc, test_next_capacity);
|
||||
tcase_add_test(tc, test_selector_error);
|
||||
tcase_add_test(tc, test_ensure_capacity);
|
||||
tcase_add_test(tc, test_selector_register_fd);
|
||||
tcase_add_test(tc, test_selector_register_unregister_register);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
main(void) {
|
||||
int number_failed;
|
||||
SRunner *sr = srunner_create(suite());
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
143
test/stm_test.c
143
test/stm_test.c
|
@ -1,143 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <check.h>
|
||||
#include "selector.h"
|
||||
#include "stm.h"
|
||||
|
||||
enum test_states {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
};
|
||||
|
||||
struct data {
|
||||
bool arrived [3];
|
||||
bool departed[3];
|
||||
unsigned i;
|
||||
};
|
||||
|
||||
static void
|
||||
on_arrival(const unsigned state, struct selector_key *key) {
|
||||
struct data *d = (struct data *)key->data;
|
||||
d->arrived[state] = true;
|
||||
}
|
||||
|
||||
static void
|
||||
on_departure(const unsigned state,struct selector_key *key) {
|
||||
struct data *d = (struct data *)key->data;
|
||||
d->departed[state] = true;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
on_read_ready(struct selector_key *key) {
|
||||
struct data *d = (struct data *)key->data;
|
||||
unsigned ret;
|
||||
|
||||
if(d->i < C) {
|
||||
ret = ++d->i;
|
||||
} else {
|
||||
ret = C;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
on_write_ready(struct selector_key *key) {
|
||||
return on_read_ready(key);
|
||||
}
|
||||
|
||||
static const struct state_definition statbl[] = {
|
||||
{
|
||||
.state = A,
|
||||
.on_arrival = on_arrival,
|
||||
.on_departure = on_departure,
|
||||
.on_read_ready = on_read_ready,
|
||||
.on_write_ready = on_write_ready,
|
||||
},{
|
||||
.state = B,
|
||||
.on_arrival = on_arrival,
|
||||
.on_departure = on_departure,
|
||||
.on_read_ready = on_read_ready,
|
||||
.on_write_ready = on_write_ready,
|
||||
},{
|
||||
.state = C,
|
||||
.on_arrival = on_arrival,
|
||||
.on_departure = on_departure,
|
||||
.on_read_ready = on_read_ready,
|
||||
.on_write_ready = on_write_ready,
|
||||
}
|
||||
};
|
||||
|
||||
//static bool init = false;
|
||||
|
||||
START_TEST (test_buffer_misc) {
|
||||
struct state_machine stm = {
|
||||
.initial = A,
|
||||
.max_state = C,
|
||||
.states = statbl,
|
||||
};
|
||||
struct data data = {
|
||||
.i = 0,
|
||||
};
|
||||
struct selector_key key = {
|
||||
.data = &data,
|
||||
};
|
||||
stm_init(&stm);
|
||||
ck_assert_uint_eq(A, stm_state(&stm));
|
||||
ck_assert_uint_eq(false, data.arrived[A]);
|
||||
ck_assert_uint_eq(false, data.arrived[B]);
|
||||
ck_assert_uint_eq(false, data.arrived[C]);
|
||||
ck_assert_ptr_null(stm.current);
|
||||
|
||||
stm_handler_read(&stm, &key);
|
||||
ck_assert_uint_eq(B, stm_state(&stm));
|
||||
ck_assert_uint_eq(true, data.arrived[A]);
|
||||
ck_assert_uint_eq(true, data.arrived[B]);
|
||||
ck_assert_uint_eq(false, data.arrived[C]);
|
||||
ck_assert_uint_eq(true, data.departed[A]);
|
||||
ck_assert_uint_eq(false, data.departed[B]);
|
||||
ck_assert_uint_eq(false, data.departed[C]);
|
||||
|
||||
stm_handler_write(&stm, &key);
|
||||
ck_assert_uint_eq(C, stm_state(&stm));
|
||||
ck_assert_uint_eq(true, data.arrived[A]);
|
||||
ck_assert_uint_eq(true, data.arrived[B]);
|
||||
ck_assert_uint_eq(true, data.arrived[C]);
|
||||
ck_assert_uint_eq(true, data.departed[A]);
|
||||
ck_assert_uint_eq(true, data.departed[B]);
|
||||
ck_assert_uint_eq(false, data.departed[C]);
|
||||
|
||||
stm_handler_read(&stm, &key);
|
||||
ck_assert_uint_eq(C, stm_state(&stm));
|
||||
ck_assert_uint_eq(true, data.arrived[A]);
|
||||
ck_assert_uint_eq(true, data.arrived[B]);
|
||||
ck_assert_uint_eq(true, data.arrived[C]);
|
||||
ck_assert_uint_eq(true, data.departed[A]);
|
||||
ck_assert_uint_eq(true, data.departed[B]);
|
||||
ck_assert_uint_eq(false, data.departed[C]);
|
||||
|
||||
stm_handler_close(&stm, &key);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *
|
||||
suite(void) {
|
||||
Suite *s = suite_create("nio_stm");
|
||||
TCase *tc = tcase_create("nio_stm");
|
||||
|
||||
tcase_add_test(tc, test_buffer_misc);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
main(void) {
|
||||
SRunner *sr = srunner_create(suite());
|
||||
int number_failed;
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
Loading…
Reference in New Issue