From c179ee88d91a84fc75dc4602cca500e8fa72ed66 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sun, 27 Apr 2014 15:04:07 +0200 Subject: initial commit This patch bootstrap the new nft-sync software. Basically, this software aims to support two different setups: 1) Rule-set repository server. The software serves the nft rule-set to clients that request the ruleset. Basically from the system that acts as repository, you have to run: # nft-sync -c ../contrib/nft-sync.conf.server Then, from the client: # nft-sync -c ../contrib/nft-sync.conf.client --fetch Which displays the nft rule-set in the standard output, so you can inspect the nft rule-set. Alternatively, the client can also retrieve and apply the nft rule-set using the pull command instead: # nft-sync -c ../contrib/nft-sync.conf.client --pull [ Note that this command above does not work in this bootstrap yet ] 2) Rule-set synchronization: In case of primary-backup and multiprimary firewall configurations, the software makes sure that the firewall cluster is deploying the same filtering policy. In this case, you have to launch the process: # nft-sync -c ../contrib/nft-sync.conf --sync [ Note that this command above does not work in this bootstrap yet ] This bootstrap provides the basic infrastructure as a proof-of-concept. Many of the necessary features are still lacking: * Implement --sync and --pull commands. * Interaction with nft through libnftnl, which allows the software to retrieve the local nft rule-set, as well as to parse it and apply it. * SSL support, specifically the repository mode needs it to make sure nobody can steal your filtering policy from the network. * IPv6 support. * Allow to serve different rule-sets in the repository mode. And many others that will be added progressively. Signed-off-by: Pablo Neira Ayuso --- src/Makefile.am | 26 +++++ src/client.c | 176 ++++++++++++++++++++++++++++++ src/config-parser.y | 143 +++++++++++++++++++++++++ src/config-scanner.l | 51 +++++++++ src/event.c | 79 ++++++++++++++ src/fd.c | 57 ++++++++++ src/logging.c | 113 ++++++++++++++++++++ src/main.c | 134 +++++++++++++++++++++++ src/msg_buff.c | 96 +++++++++++++++++ src/server.c | 164 ++++++++++++++++++++++++++++ src/tcp.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/timer.c | 50 +++++++++ 12 files changed, 1384 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/client.c create mode 100644 src/config-parser.y create mode 100644 src/config-scanner.l create mode 100644 src/event.c create mode 100644 src/fd.c create mode 100644 src/logging.c create mode 100644 src/main.c create mode 100644 src/msg_buff.c create mode 100644 src/server.c create mode 100644 src/tcp.c create mode 100644 src/timer.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..5c09b24 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,26 @@ +include $(top_srcdir)/Make_global.am + +sbin_PROGRAMS = nft-sync + +AM_YFLAGS = -d + +CLEANFILES = config-parser.c \ + config-scanner.c + +nft_sync_SOURCES = event.c \ + logging.c \ + msg_buff.c \ + server.c \ + client.c \ + tcp.c \ + timer.c \ + main.c \ + fd.c \ + config-parser.y \ + config-scanner.l +nft_sync_LDADD = ${LIBMNL_LIBS} ${LIBNFTNL_LIBS} -lev + +# yacc and lex generate dirty code +config-scanner.o config-parser.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls + +EXTRA_DIST = config-parser.h diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..d509a52 --- /dev/null +++ b/src/client.c @@ -0,0 +1,176 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "init.h" +#include "logging.h" +#include "msg_buff.h" +#include "proto.h" +#include "config.h" + +static void print_payload(struct msg_buff *msgb) +{ + write(1, msgb_data(msgb) + sizeof(struct nft_sync_hdr), + msgb_len(msgb) - sizeof(struct nft_sync_hdr)); + write(1, "\n", 1); +} + +static int process_response(struct msg_buff *msgb, int len) +{ + switch (nfts_inst.cmd) { + case NFTS_CMD_NONE: + break; + case NFTS_CMD_FETCH: + print_payload(msgb); + /* We're done, stop running this process */ + nfts_inst.stop = true; + return 0; + /* TODO: We'll have a pull command at some point, the code to parse + * the xml/json ruleset should go here. + */ + default: + break; + } + return -1; +} + +static void tcp_client_established_cb(struct nft_fd *nfd, uint32_t mask) +{ + struct tcp_client *c = nfd->data; + struct nft_sync_hdr *hdr; + char buf[sizeof(struct nft_sync_hdr)]; + struct msg_buff *msgb = tcp_client_get_data(c); + int ret, len; + + if (msgb == NULL) { + /* Retrieve the header first to know the response length */ + ret = tcp_client_recv(c, buf, sizeof(buf)); + if (ret < 0) { + nfts_log(NFTS_LOG_ERROR, "cannot received from socket"); + goto err1; + } else if (ret == 0) { + nfts_log(NFTS_LOG_ERROR, + "connection from server has been closed\n"); + /* FIXME retry every N seconds using a timer, + * otherwise this sucks up the CPU by retrying to + * connect very hard. + */ + goto err1; + } + + hdr = (struct nft_sync_hdr *)buf; + len = ntohl(hdr->len); + + /* Allocate a message for the entire response */ + msgb = msgb_alloc(len); + if (msgb == NULL) { + nfts_log(NFTS_LOG_ERROR, "OOM"); + goto err1; + } + memcpy(msgb_data(msgb), buf, sizeof(buf)); + msgb_put(msgb, sizeof(buf)); + + /* Attach this message to the client */ + tcp_client_set_data(c, msgb); + } + + /* Retrieve as much data as we can in this round */ + ret = tcp_client_recv(c, msgb_tail(msgb), + msgb_size(msgb) - msgb_len(msgb)); + if (ret < 0) { + nfts_log(NFTS_LOG_ERROR, "cannot received from socket"); + goto err1; + } else if (ret == 0) { + nfts_log(NFTS_LOG_ERROR, + "connection from server has been closed\n"); + goto err1; + } + msgb_put(msgb, ret); + + /* Not enough data to process the response yet */ + if (msgb_len(msgb) < msgb_size(msgb)) + return; + + if (process_response(msgb, len) < 0) { + nfts_log(NFTS_LOG_ERROR, "discarding malformed response"); + goto err1; + } + /* Detach this message from the client */ + tcp_client_set_data(c, NULL); +err1: + msgb_free(msgb); + close(tcp_client_get_fd(c)); + nft_fd_unregister(nfd); + tcp_client_destroy(c); +} + +static void tcp_client_connect_cb(struct nft_fd *nfd, uint32_t mask) +{ + struct nft_sync_hdr *hdr; + struct tcp_client *c = nfd->data; + struct msg_buff *msgb; + int len; + + msgb = msgb_alloc(NFTS_MAX_REQUEST); + if (msgb == NULL) { + nfts_log(NFTS_LOG_ERROR, "OOM"); + return; + } + + switch (nfts_inst.cmd) { + case NFTS_CMD_FETCH: + len = strlen("fetch") + sizeof(struct nft_sync_hdr); + hdr = msgb_put(msgb, sizeof(struct nft_sync_hdr)); + hdr->len = htonl(len); + memcpy(hdr->data, "fetch", strlen("fetch")); + msgb_put(msgb, strlen("fetch")); + break; + default: + nfts_log(NFTS_LOG_ERROR, "Unknown command"); + return; + } + + if (tcp_client_send(c, msgb_data(msgb), msgb_len(msgb)) < 0) { + nfts_log(NFTS_LOG_ERROR, "cannot send to socket: %s", + strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Now that we got connected, register the descriptor again to + * permanently listen for incoming data. + */ + nft_fd_setup(&nfts_inst.tcp_client_nfd, tcp_client_get_fd(c), + tcp_client_established_cb, c); + nft_fd_register(nfd, EV_READ | EV_PERSIST); +} + +int tcp_client_start(struct nft_sync_inst *inst) +{ + struct tcp_client *c; + + c = tcp_client_create(&inst->tcp); + if (c == NULL) { + fprintf(stderr, "cannot initialize TCP client\n"); + return -1; + } + + nft_fd_setup(&inst->tcp_client_nfd, tcp_client_get_fd(c), + tcp_client_connect_cb, c); + nft_fd_register(&inst->tcp_client_nfd, EV_WRITE); + + return 0; +} diff --git a/src/config-parser.y b/src/config-parser.y new file mode 100644 index 0000000..41c37b9 --- /dev/null +++ b/src/config-parser.y @@ -0,0 +1,143 @@ +%{ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "logging.h" + +extern char *yytext; +extern int yylineno; + +static int parse_addr(const char *text, struct in_addr *addr, + uint16_t *port) +{ + char *colon = strchr(text, ':'); + + if (colon == NULL) { + fprintf(stderr, "missing `:' to indicate port\n"); + return -1; + } + *colon = '\0'; + + if (inet_pton(AF_INET, text, addr) < 0) { + fprintf(stderr, "%s not valid IPv4 address\n", text); + return -1; + } + *port = atoi(colon + 1); + + return 0; +} + +%} + +%union { + int val; + char *string; +} + +%token T_LOCAL_ADDR +%token T_REMOTE_ADDR +%token T_ADDR +%token T_NUMBER +%token T_LOG +%token T_MODE + +%token T_STRING +%token T_INTEGER + +%% + +configfile : + | sections + ; + +sections : section + | sections section + ; + +section : network + | log + ; + +network : local_addr + | remote_addr + ; + +local_addr : T_LOCAL_ADDR T_STRING + { + nfts_inst.tcp.ipproto = AF_INET; + if (parse_addr($2, + &nfts_inst.tcp.server.ipv4.inet_addr, + &nfts_inst.tcp.port) < 0) + break; + + nfts_inst.mode = NFTS_MODE_SERVER; + } + ; + +remote_addr : T_REMOTE_ADDR T_STRING + { + nfts_inst.tcp.ipproto = AF_INET; + if (parse_addr($2, &nfts_inst.tcp.client.inet_addr, + &nfts_inst.tcp.port) < 0) + break; + + nfts_inst.mode = NFTS_MODE_CLIENT; + } + ; + +log : T_LOG T_STRING + { + if (strcmp($2, "syslog") == 0) { + nfts_inst.log.type = NFTS_LOG_T_SYSLOG; + } else if (strcmp($2, "stdout") == 0) { + nfts_inst.log.type = NFTS_LOG_T_FILE; + nfts_inst.log.color = true; + } else { + nfts_inst.log.type = NFTS_LOG_T_FILE; + strncpy(nfts_inst.log.filename, $2, PATH_MAX); + nfts_inst.log.filename[PATH_MAX - 1] = '\0'; + } + } + ; + +%% + +int __attribute__((noreturn)) yyerror(char *msg) +{ + fprintf(stderr, "parsing config file in line (%d), symbol '%s': %s\n", + yylineno, yytext, msg); + exit(EXIT_FAILURE); +} + +int nft_sync_config_parse(const char *filename) +{ + FILE *fp; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Cannot open configuration file %s\n", + filename); + return -1; + } + + yyrestart(fp); + yyparse(); + fclose(fp); + + return 0; +} diff --git a/src/config-scanner.l b/src/config-scanner.l new file mode 100644 index 0000000..d3ad91e --- /dev/null +++ b/src/config-scanner.l @@ -0,0 +1,51 @@ +%{ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include "config-parser.h" +%} + +%option yylineno +%option noinput +%option nounput + +ws [ \t]+ +comment #.*$ +nl [\n\r] + +is_on [o|O][n|N] +is_off [o|O][f|F][f|F] +integer [\-\+]?[0-9]+ +string [a-zA-Z0-9][a-zA-Z0-9\.\-\_\/\:]* + +%% +"local-address" { return T_LOCAL_ADDR; } +"remote-address" { return T_REMOTE_ADDR; } +"logging" { return T_LOG; } +"mode" { return T_MODE; } + +{integer} { yylval.val = atoi(yytext); return T_INTEGER; } +{string} { yylval.string = strdup(yytext); return T_STRING; } + +{comment} ; +{ws} ; +{nl} ; + +<> { yyterminate(); } + +. { return yytext[0]; } + +%% + +int +yywrap() +{ + return 1; +} diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..464b689 --- /dev/null +++ b/src/event.c @@ -0,0 +1,79 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ +#include +#include +#include + +#include "init.h" +#include "logging.h" + +static int sigtype; +static struct event_base *ev_base; +static struct event sigterm_event, sigusr1_event, sigint_event; + +static void sigterm_callback(int fd, short event, void *data) +{ + sigtype = SIGTERM; +} + +static void sigint_callback(int fd, short event, void *data) +{ + sigtype = SIGINT; +} + +static void sigusr1_callback(int fd, short event, void *data) +{ + sigtype = SIGUSR1; +} + +int nft_sync_event_init(void) +{ + ev_base = event_init(); + if (ev_base == NULL) + return -1; + + signal_set(&sigint_event, SIGINT, sigint_callback, NULL); + signal_add(&sigint_event, NULL); + signal_set(&sigterm_event, SIGTERM, sigterm_callback, NULL); + signal_add(&sigterm_event, NULL); + signal_set(&sigusr1_event, SIGUSR1, sigusr1_callback, NULL); + signal_add(&sigusr1_event, NULL); + + return 0; +} + +void nft_sync_event_loop(void) +{ + while (!sigtype && !nfts_inst.stop) + event_loop(EVLOOP_ONCE); + + switch (sigtype) { + case SIGINT: + nfts_log(NFTS_LOG_NOTICE, "Received SIGINT, closing."); + break; + case SIGTERM: + nfts_log(NFTS_LOG_NOTICE, "Received SIGTERM, closing."); + break; + case SIGUSR1: + nfts_log(NFTS_LOG_NOTICE, "Received SIGUSR1"); + /* TODO: reload configuration file */ + break; + default: + nfts_log(NFTS_LOG_INFO, "Closing process"); + break; + } +} + +void nft_sync_event_fini(void) +{ + signal_del(&sigterm_event); + signal_del(&sigusr1_event); + signal_del(&sigint_event); + event_base_free(ev_base); +} diff --git a/src/fd.c b/src/fd.c new file mode 100644 index 0000000..46b443a --- /dev/null +++ b/src/fd.c @@ -0,0 +1,57 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include + +static void nft_fd_cb(int _fd, short mask, void *data) +{ + struct nft_fd *nfd = data; + + nfd->cb(nfd, mask); +} + +void nft_fd_setup(struct nft_fd *nfd, int fd, + void (*cb)(struct nft_fd *fd, uint32_t mask), void *data) +{ + /* add assertion */ + + nfd->fd = fd; + nfd->cb = cb; + nfd->data = data; +} + +void nft_fd_register(struct nft_fd *nfd, uint32_t events) +{ + unsigned short mask = events; + + /* add assertion */ + + event_set(&nfd->event, nfd->fd, mask, nft_fd_cb, nfd); + event_add(&nfd->event, NULL); +} + +void nft_fd_unregister(struct nft_fd *fd) +{ + /* add assertion */ + event_del(&fd->event); + fd->fd = -1; +} + +struct nft_fd *nft_fd_alloc(void) +{ + return calloc(1, sizeof(struct nft_fd)); +} + +void nft_fd_free(struct nft_fd *nfd) +{ + free(nfd); +} diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000..9907e5f --- /dev/null +++ b/src/logging.c @@ -0,0 +1,113 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include "config.h" +#include "logging.h" + +static struct { + const char *text; + const char *color; +} logging[NFTS_LOG_MAX] = { + [NFTS_LOG_DEBUG] = { + .text = "DEBUG", + .color = "\033[1;33m", + }, + [NFTS_LOG_INFO] = { + .text = "INFO", + .color = "\033[1;32m", + }, + [NFTS_LOG_NOTICE] = { + .text = "NOTICE", + .color = "\033[1;36m", + }, + [NFTS_LOG_ERROR] = { + .text = "ERROR", + .color = "\033[1;33m", + }, + [NFTS_LOG_FATAL] = { + .text = "FATAL", + .color = "\033[1;31m", + }, +}; + +int nft_sync_log_init(struct nft_sync_inst *inst) +{ + int ret = 0; + + switch (inst->log.type) { + case NFTS_LOG_T_SYSLOG: + break; + case NFTS_LOG_T_FILE: + if (inst->log.fd == NULL) + inst->log.fd = stdout; + else { + inst->log.fd = fopen(inst->log.filename, "w+"); + if (inst->log.fd == NULL) + return -1; + } + break; + } + + return ret; +} + +void nft_sync_log_fini(struct nft_sync_inst *inst) +{ + switch (inst->log.type) { + case NFTS_LOG_T_SYSLOG: + break; + case NFTS_LOG_T_FILE: + if (inst->log.fd != NULL) + fclose(inst->log.fd); + break; + } +} + +void nft_sync_log(struct nft_sync_inst *inst, int prio, + const char *format, ...) +{ + time_t t; + char *timebuf = NULL; + va_list args; + + switch (inst->log.type) { + case NFTS_LOG_T_FILE: + t = time(NULL); + timebuf = ctime(&t); + timebuf[strlen(timebuf) - 1]='\0'; + break; + case NFTS_LOG_T_SYSLOG: + break; + } + + switch (inst->log.type) { + case NFTS_LOG_T_FILE: + va_start(args, format); + fprintf(inst->log.fd, "%s[%s] [%s] ", + inst->log.color ? logging[prio].color : "", timebuf, + logging[prio].text); + vfprintf(inst->log.fd, format, args); + va_end(args); + fprintf(inst->log.fd, "%s\n", + inst->log.color ? "\033[1;0m" : ""); + fflush(inst->log.fd); + break; + case NFTS_LOG_T_SYSLOG: + va_start(args, format); + vsyslog(prio, format, args); + va_end(args); + break; + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..af0a7a5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,134 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Thanks to the NLnet Foundation for making the bootstrap + * of this project possible! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "init.h" +#include "logging.h" +#include "msg_buff.h" +#include "proto.h" + +struct nft_sync_inst nfts_inst; + +static void print_usage(const char *prog_name) +{ + fprintf(stderr, + "%s (c) 2014 by Pablo Neira Ayuso \n" + "Usage: %s [-h] [-c]\n" + " [ --help ]\n" + " [ --config= ]\n" + " [ --fetch ]\n", prog_name, prog_name); +} + +static const struct option options[] = { + { .name = "help", .has_arg = false, .val = 'h' }, + { .name = "config", .has_arg = false, .val = 'c' }, + { .name = "fetch", .has_arg = false, .val = 'f' }, + { NULL }, +}; + +#define NFT_SYNC_CONF_DEFAULT "/etc/nft-sync.conf" + +static int set_cmd(int cmd) +{ + if (nfts_inst.cmd) { + fprintf(stderr, + "Cannot specify multiple commands at the same time\n"); + return -1; + } + nfts_inst.cmd = cmd; + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE, c; + const char *config = NFT_SYNC_CONF_DEFAULT; + + while ((c = getopt_long(argc, argv, "hc:f", options, NULL)) != -1) { + switch (c) { + case 'h': + print_usage(argv[0]); + return EXIT_SUCCESS; + case 'c': + config = optarg; + break; + case 'f': + set_cmd(NFTS_CMD_FETCH); + break; + default: + fprintf(stderr, "Unknown option -%c\n", c); + return EXIT_FAILURE; + } + } + + if (nft_sync_config_parse(config) < 0) + return EXIT_FAILURE; + + if (nft_sync_event_init() < 0) { + fprintf(stderr, "Cannot start libev: %s\n", strerror(errno)); + goto err; + } + + if (nft_sync_log_init(&nfts_inst) < 0) { + fprintf(stderr, "Cannot start logging: %s\n", strerror(errno)); + goto err; + } + + if (nfts_inst.mode & NFTS_MODE_SERVER) { + if (tcp_server_start(&nfts_inst) < 0) { + nfts_log(NFTS_LOG_FATAL, + "Cannot start TCP server: %s\n", + strerror(errno)); + goto err; + } + nfts_log(NFTS_LOG_INFO, "listening at %s", + inet_ntoa(nfts_inst.tcp.server.ipv4.inet_addr)); + } + + if (nfts_inst.mode & NFTS_MODE_CLIENT) { + if (!nfts_inst.cmd) { + nfts_log(NFTS_LOG_FATAL, + "Client needs some command, eg. --fetch", + strerror(errno)); + goto err; + } + if (tcp_client_start(&nfts_inst) < 0) { + nfts_log(NFTS_LOG_FATAL, + "Cannot start TCP client: %s", + strerror(errno)); + goto err; + } + nfts_log(NFTS_LOG_INFO, "connecting to %s", + inet_ntoa(nfts_inst.tcp.client.inet_addr)); + } + + /* TODO: add switch to allow to daemonize this process */ + + nft_sync_event_loop(); + + nft_sync_event_fini(); + + ret = EXIT_SUCCESS; +err: + nft_sync_log_fini(&nfts_inst); + + return ret; +} diff --git a/src/msg_buff.c b/src/msg_buff.c new file mode 100644 index 0000000..c148516 --- /dev/null +++ b/src/msg_buff.c @@ -0,0 +1,96 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include "msg_buff.h" + +struct msg_buff { + uint16_t len; + unsigned char *head; + unsigned char *data; + unsigned char *tail; + unsigned char *end; + + unsigned char _data[0]; +}; + +struct msg_buff *msgb_alloc(uint32_t size) +{ + struct msg_buff *msgb; + + msgb = malloc(sizeof(struct msg_buff) + size); + if (msgb == NULL) + return NULL; + + msgb->len = 0; + msgb->head = msgb->_data; + msgb->data = msgb->tail = msgb->_data; + msgb->end = msgb->_data + size; + + return msgb; +} + +void msgb_free(struct msg_buff *msgb) +{ + free(msgb); +} + +uint32_t msgb_size(struct msg_buff *msgb) +{ + return msgb->end - msgb->head; +} + +uint32_t msgb_len(struct msg_buff *msgb) +{ + return msgb->len; +} + +void *msgb_put(struct msg_buff *msgb, uint32_t len) +{ + void *data = msgb->tail; + + msgb->len += len; + msgb->tail += len; + + return data; +} + +void *msgb_pull(struct msg_buff *msgb, uint32_t len) +{ + void *ptr = msgb->data; + + if (len > msgb->len) + return NULL; + + msgb->len -= len; + msgb->data += len; + + return ptr; +} + +unsigned char *msgb_data(struct msg_buff *msgb) +{ + return msgb->data; +} + +unsigned char *msgb_tail(struct msg_buff *msgb) +{ + return msgb->tail; +} + +void msgb_burp(struct msg_buff *msgb) +{ + void *data = msgb->data; + int len = msgb->len; + + msgb->data = msgb->head; + memcpy(msgb->data, data, len); +} diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..cd4ac0a --- /dev/null +++ b/src/server.c @@ -0,0 +1,164 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "init.h" +#include "logging.h" +#include "msg_buff.h" +#include "proto.h" +#include "config.h" +#include "proto.h" + +static int send_ruleset(struct nft_fd *nfd) +{ + struct msg_buff *msgb; + struct nft_sync_hdr *hdr; + /* TODO: send real ruleset in json/xml format here, replace this + * code with the real libnftnl code. + */ + const char *ruleset = "this is the ruleset in XML/JSON format"; + int ret, ruleset_len = strlen(ruleset); + + msgb = msgb_alloc(sizeof(struct nft_sync_hdr) + ruleset_len); + if (msgb == NULL) + return -1; + + hdr = msgb_put(msgb, sizeof(struct nft_sync_hdr) + ruleset_len); + hdr->len = htonl(sizeof(struct nft_sync_hdr) + ruleset_len); + memcpy(hdr->data, ruleset, ruleset_len); + + ret = send(nfd->fd, msgb_data(msgb), msgb_len(msgb), 0); + msgb_free(msgb); + + return ret; +} + +static int nfts_parse_request(struct nft_fd *nfd, const char *req) +{ + int ret = -1; + + if (strncmp(req, "fetch", strlen("fetch")) == 0) + ret = send_ruleset(nfd); + + return ret; +} + +static void tcp_server_established_cb(struct nft_fd *nfd, uint32_t mask) +{ + struct msg_buff *msgb = nfd->data; + struct nft_sync_hdr *hdr; + uint32_t len; + int ret; + + ret = recv(nfd->fd, msgb_tail(msgb), + msgb_size(msgb) - msgb_len(msgb), 0); + if (ret == 0) + goto err1; + else if (ret < 0) { + nfts_log(NFTS_LOG_ERROR, "cannot receive from client"); + goto err1; + } + msgb_put(msgb, ret); + + /* Not enough room for header yet, grab more bytes later */ + if (msgb_len(msgb) < sizeof(struct nft_sync_hdr)) + return; + + hdr = (struct nft_sync_hdr *) msgb_data(msgb); + + len = ntohl(hdr->len); + + if (len >= NFTS_MAX_REQUEST) { + nfts_log(NFTS_LOG_ERROR, "discarding message too large %d", + len, NFTS_MAX_REQUEST); + goto err1; + } + + /* Not enough data to process this request yet */ + if (len < (uint32_t)ret) + return; + + hdr = msgb_pull(msgb, len); + if (hdr == NULL) { + nfts_log(NFTS_LOG_FATAL, "cannot pull out header"); + goto err1; + } + + if (nfts_parse_request(nfd, hdr->data) < 0) { + nfts_log(NFTS_LOG_ERROR, "discarding malformed request"); + goto err1; + } + + /* There's still some pending bytes from the stream in the message, + * move them at the head of the message buffer. + */ + if (msgb_len(msgb) > 0) + msgb_burp(msgb); + + return; +err1: + nfts_log(NFTS_LOG_NOTICE, "closing connection"); + msgb_free(msgb); + close(nfd->fd); + nft_fd_unregister(nfd); + nft_fd_free(nfd); +} + +static void tcp_server_cb(struct nft_fd *nfd, uint32_t mask) +{ + struct nft_fd *accept_nfd; + struct msg_buff *msgb; + struct sockaddr_in addr; + int fd; + + msgb = msgb_alloc(NFTS_MAX_REQUEST); + if (msgb == NULL) { + nfts_log(NFTS_LOG_ERROR, "OOM"); + return; + } + + fd = tcp_server_accept(nfd->data, &addr); + if (fd < 0) { + msgb_free(msgb); + nfts_log(NFTS_LOG_ERROR, "failed to accept socket"); + return; + } + nfts_log(NFTS_LOG_NOTICE, "accepted new connection from %s", + inet_ntoa(addr.sin_addr)); + + accept_nfd = nft_fd_alloc(); + nft_fd_setup(accept_nfd, fd, tcp_server_established_cb, msgb); + nft_fd_register(accept_nfd, EV_READ | EV_PERSIST); +} + +int tcp_server_start(struct nft_sync_inst *inst) +{ + struct tcp_server *s; + + nfts_inst.tcp.ipproto = AF_INET; + nfts_inst.tcp.port = 1234; + + s = tcp_server_create(&inst->tcp); + if (s == NULL) + return -1; + + nft_fd_setup(&inst->tcp_server_fd, tcp_server_get_fd(s), + tcp_server_cb, s); + nft_fd_register(&inst->tcp_server_fd, EV_READ | EV_PERSIST); + + return 0; +} diff --git a/src/tcp.c b/src/tcp.c new file mode 100644 index 0000000..ced350a --- /dev/null +++ b/src/tcp.c @@ -0,0 +1,295 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "logging.h" + +/* + * TCP server side + */ + +struct tcp_server { + int fd; + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; +}; + +#define TCP_SERVER_LISTEN 20 + +struct tcp_server *tcp_server_create(struct tcp_conf *conf) +{ + int ret, on = 1; + struct tcp_server *c; + socklen_t socklen = sizeof(int); + + c = calloc(1, sizeof(struct tcp_server)); + if (c == NULL) + return NULL; + + switch (conf->ipproto) { + case AF_INET: + c->addr.ipv4.sin_family = AF_INET; + c->addr.ipv4.sin_port = htons(conf->port); + c->addr.ipv4.sin_addr = conf->server.ipv4.inet_addr; + socklen = sizeof(struct sockaddr_in); + break; + + case AF_INET6: + c->addr.ipv6.sin6_family = AF_INET6; + c->addr.ipv6.sin6_port = htons(conf->port); + c->addr.ipv6.sin6_addr = conf->server.ipv6.inet_addr6; + c->addr.ipv6.sin6_scope_id = conf->server.ipv6.scope_id; + socklen = sizeof(struct sockaddr_in6); + break; + } + + c->fd = socket(conf->ipproto, SOCK_STREAM, 0); + if (c->fd < 0) + goto err1; + + ret = setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)); + if (ret < 0) + goto err2; + + ret = setsockopt(c->fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(int)); + if (ret < 0) + goto err2; + + ret = bind(c->fd, (struct sockaddr *) &c->addr, socklen); + if (ret < 0) + goto err2; + + ret = listen(c->fd, TCP_SERVER_LISTEN); + if (ret < 0) + goto err2; + + ret = fcntl(c->fd, F_SETFL, O_NONBLOCK); + if (ret < 0) + goto err2; + + return c; +err2: + close(c->fd); +err1: + free(c); + return NULL; +} + +void tcp_server_destroy(struct tcp_server *c) +{ + close(c->fd); + free(c); +} + +int tcp_server_get_fd(struct tcp_server *c) +{ + return c->fd; +} + +int tcp_server_accept(struct tcp_server *c, struct sockaddr_in *addr) +{ + int err, fd; + socklen_t socklen = sizeof(struct sockaddr_in); + + err = accept(c->fd, (struct sockaddr *)addr, &socklen); + if (err < 0 && errno != EAGAIN) + return -1; + + fd = err; + + err = fcntl(fd, F_SETFL, O_NONBLOCK); + if (err < 0) { + close(fd); + return -1; + } + + return fd; +} + +/* + * TCP client side + */ + +enum tcp_client_state { + TCP_DISCONNECTED = 0, + TCP_CONNECTING, + TCP_CONNECTED +}; + +struct tcp_client { + int fd; + enum tcp_client_state state; + union { + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; + socklen_t socklen; + struct nft_timer timer; + void *data; +}; + +#define TCP_CONNECT_TIMEOUT 1 + +static int tcp_client_init(struct tcp_client *c, struct tcp_conf *conf) +{ + int ret = 0; + + c->fd = socket(conf->ipproto, SOCK_STREAM, 0); + if (c->fd < 0) + return -1; + + switch (conf->ipproto) { + case AF_INET: + c->addr.ipv4.sin_family = AF_INET; + c->addr.ipv4.sin_port = htons(conf->port); + c->addr.ipv4.sin_addr = conf->client.inet_addr; + c->socklen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + c->addr.ipv6.sin6_family = AF_INET6; + c->addr.ipv6.sin6_port = htons(conf->port); + c->addr.ipv6.sin6_addr = conf->client.inet_addr6; + c->socklen = sizeof(struct sockaddr_in6); + break; + default: + ret = -1; + break; + } + + if (ret < 0) + goto err1; + + ret = fcntl(c->fd, F_SETFL, O_NONBLOCK); + if (ret < 0) + goto err1; + + ret = connect(c->fd, (struct sockaddr *)&c->addr, c->socklen); + if (ret < 0) { + switch (errno) { + case EINPROGRESS: + c->state = TCP_CONNECTING; + break; + default: /* ECONNREFUSED */ + c->state = TCP_DISCONNECTED; + goto err1; + } + } else { + /* very unlikely at this stage. */ + c->state = TCP_CONNECTED; + } + return 0; +err1: + close(c->fd); + return ret; +} + +int tcp_client_get_fd(struct tcp_client *c) +{ + return c->fd; +} + +struct tcp_client *tcp_client_create(struct tcp_conf *conf) +{ + struct tcp_client *c; + + c = calloc(1, sizeof(struct tcp_client)); + if (c == NULL) + return NULL; + + if (tcp_client_init(c, conf) < 0) { + free(c); + return NULL; + } + + return c; +} + +void tcp_client_destroy(struct tcp_client *c) +{ + close(c->fd); + free(c); +} + +ssize_t tcp_client_send(struct tcp_client *c, const void *data, int size) +{ + ssize_t ret = 0; + + switch (c->state) { + case TCP_DISCONNECTED: + ret = -1; + break; + case TCP_CONNECTING: + ret = connect(c->fd, (struct sockaddr *)&c->addr, c->socklen); + if (ret < 0) + return ret; + + c->state = TCP_CONNECTED; + /* fall through ... */ + case TCP_CONNECTED: + ret = send(c->fd, data, size, 0); + if (ret <= 0) { + /* errno == EPIPE || errno == ECONNRESET */ + c->state = TCP_DISCONNECTED; + return ret; + } + break; + } + return ret; +} + +ssize_t tcp_client_recv(struct tcp_client *c, void *data, int size) +{ + ssize_t ret = 0; + + switch (c->state) { + case TCP_DISCONNECTED: + ret = -1; + break; + case TCP_CONNECTING: + ret = connect(c->fd, (struct sockaddr *)&c->addr, c->socklen); + if (ret < 0) + return ret; + + c->state = TCP_CONNECTED; + /* fall through ... */ + case TCP_CONNECTED: + ret = recv(c->fd, data, size, 0); + if (ret <= 0) { + /* errno == ENOTCONN */ + c->state = TCP_DISCONNECTED; + return ret; + } + } + return ret; +} + +void tcp_client_set_data(struct tcp_client *c, void *data) +{ + c->data = data; +} + +void *tcp_client_get_data(struct tcp_client *c) +{ + return c->data; +} diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..7e39076 --- /dev/null +++ b/src/timer.c @@ -0,0 +1,50 @@ +/* + * (C) 2014 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#include "timer.h" + +void *nft_timer_data(struct nft_timer *timer) +{ + return timer->data; +} + +static void nft_timer_callback(int fd, short mask, void *data) +{ + struct nft_timer *timer = data; + + timer->callback(timer); +} + +void nft_timer_setup(struct nft_timer *timer, void (*cb)(struct nft_timer *), + void *data) +{ + // assert: evtimer_pending(timer->event, NULL) == 0; + timer->callback = cb; +} + +void nft_timer_add(struct nft_timer *timer, unsigned int sec, + unsigned int usec) +{ + struct timeval tv = { + .tv_sec = sec, + .tv_usec = usec, + }; + + if (evtimer_pending(&timer->event, NULL)) + evtimer_del(&timer->event); + + evtimer_set(&timer->event, nft_timer_callback, timer); + evtimer_add(&timer->event, &tv); +} + +void nft_timer_del(struct nft_timer *timer) +{ + evtimer_del(&timer->event); +} -- cgit v1.2.3