diff options
author | ambrop7 <ambrop7@1a93d707-3861-5ebc-ad3b-9740d49b5140> | 2011-05-07 21:12:16 +0400 |
---|---|---|
committer | ambrop7 <ambrop7@1a93d707-3861-5ebc-ad3b-9740d49b5140> | 2011-05-07 21:12:16 +0400 |
commit | dc923d511818267004fc90dde5db8167b86cd82a (patch) | |
tree | cdbabfa456134fab96afb72b1f3ec833ae52aceb /udpgw | |
parent | 120ee9ea484465b2cb2e3558ee9613f9256f9114 (diff) |
add udpgw
Diffstat (limited to 'udpgw')
-rw-r--r-- | udpgw/CMakeLists.txt | 9 | ||||
-rw-r--r-- | udpgw/udpgw.c | 1103 | ||||
-rw-r--r-- | udpgw/udpgw.h | 48 |
3 files changed, 1160 insertions, 0 deletions
diff --git a/udpgw/CMakeLists.txt b/udpgw/CMakeLists.txt new file mode 100644 index 0000000..db87fb0 --- /dev/null +++ b/udpgw/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(badvpn-udpgw + udpgw.c +) +target_link_libraries(badvpn-udpgw system flow) + +install( + TARGETS badvpn-udpgw + RUNTIME DESTINATION bin +) diff --git a/udpgw/udpgw.c b/udpgw/udpgw.c new file mode 100644 index 0000000..4a1f5a9 --- /dev/null +++ b/udpgw/udpgw.c @@ -0,0 +1,1103 @@ +/** + * @file udpgw.c + * @author Ambroz Bizjak <ambrop7@gmail.com> + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <protocol/udpgw_proto.h> +#include <misc/debug.h> +#include <misc/version.h> +#include <misc/loggers_string.h> +#include <misc/loglevel.h> +#include <misc/offset.h> +#include <misc/byteorder.h> +#include <misc/bsize.h> +#include <structure/LinkedList1.h> +#include <structure/BAVL.h> +#include <system/BLog.h> +#include <system/BReactor.h> +#include <system/BSocket.h> +#include <system/BSignal.h> +#include <system/Listener.h> +#include <flow/StreamSocketSource.h> +#include <flow/PacketProtoDecoder.h> +#include <flow/DatagramSocketSink.h> +#include <flow/PacketPassFairQueue.h> +#include <flow/PacketStreamSender.h> +#include <flow/StreamSocketSink.h> +#include <flow/PacketProtoFlow.h> +#include <flow/DatagramSocketSink.h> +#include <flow/DatagramSocketSource.h> +#include <flow/SinglePacketBuffer.h> + +#ifndef BADVPN_USE_WINAPI +#include <system/BLog_syslog.h> +#endif + +#include <udpgw/udpgw.h> + +#include <generated/blog_channel_udpgw.h> + +#define LOGGER_STDOUT 1 +#define LOGGER_SYSLOG 2 + +struct client { + BSocket sock; + BAddr addr; + BTimer disconnect_timer; + FlowErrorDomain domain; + StreamSocketSource recv_source; + PacketProtoDecoder recv_decoder; + PacketPassInterface recv_if; + PacketPassFairQueue send_queue; + PacketStreamSender send_sender; + StreamSocketSink send_sink; + BAVL connections_tree; + LinkedList1 connections_list; + int num_connections; + LinkedList1 closing_connections_list; + LinkedList1Node clients_list_node; +}; + +struct connection { + struct client *client; + uint16_t conid; + BAddr addr; + const uint8_t *first_data; + int first_data_len; + int closing; + BPending first_job; + BufferWriter *send_if; + PacketProtoFlow send_ppflow; + PacketPassFairQueueFlow send_qflow; + union { + struct { + BSocket udp_sock; + FlowErrorDomain udp_domain; + BufferWriter udp_send_writer; + PacketBuffer udp_send_buffer; + DatagramSocketSink udp_send_sink; + DatagramSocketSource udp_recv_source; + SinglePacketBuffer udp_recv_buffer; + PacketPassInterface udp_recv_if; + BAVLNode connections_tree_node; + LinkedList1Node connections_list_node; + }; + struct { + LinkedList1Node closing_connections_list_node; + }; + }; +}; + +// command-line options +struct { + int help; + int version; + int logger; + #ifndef BADVPN_USE_WINAPI + char *logger_syslog_facility; + char *logger_syslog_ident; + #endif + int loglevel; + int loglevels[BLOG_NUM_CHANNELS]; + char *listen_addrs[MAX_LISTEN_ADDRS]; + int num_listen_addrs; + int udp_mtu; + int max_clients; + int max_connections_for_client; +} options; + +// MTUs +int udpgw_mtu; +int pp_mtu; + +// listen addresses +BAddr listen_addrs[MAX_LISTEN_ADDRS]; +int num_listen_addrs; + +// reactor +BReactor ss; + +// listeners +Listener listeners[MAX_LISTEN_ADDRS]; +int num_listeners; + +// clients +LinkedList1 clients_list; +int num_clients; + +static void print_help (const char *name); +static void print_version (void); +static int parse_arguments (int argc, char *argv[]); +static int process_arguments (void); +static void signal_handler (void *unused); +static void listener_handler (Listener *listener); +static void client_free (struct client *client); +static void client_log (struct client *client, int level, const char *fmt, ...); +static void client_disconnect_timer_handler (struct client *client); +static void client_error_handler (struct client *client, int component, int code); +static void client_recv_if_handler_send (struct client *client, uint8_t *data, int data_len); +static void connection_init (struct client *client, uint16_t conid, BAddr addr, const uint8_t *data, int data_len); +static void connection_free (struct connection *con); +static void connection_log (struct connection *con, int level, const char *fmt, ...); +static void connection_free_udp (struct connection *con); +static void connection_first_job_handler (struct connection *con); +static int connection_send_to_client (struct connection *con, uint8_t flags, const uint8_t *data, int data_len); +static int connection_send_to_udp (struct connection *con, const uint8_t *data, int data_len); +static void connection_close (struct connection *con); +static void connection_send_qflow_busy_handler (struct connection *con); +static void connection_udp_error_handler (struct connection *con, int component, int code); +static void connection_udp_recv_if_handler_send (struct connection *con, uint8_t *data, int data_len); +static struct connection * find_connection (struct client *client, uint16_t conid); +static int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2); + +int main (int argc, char **argv) +{ + if (argc <= 0) { + return 1; + } + + // parse command-line arguments + if (!parse_arguments(argc, argv)) { + fprintf(stderr, "Failed to parse arguments\n"); + print_help(argv[0]); + goto fail0; + } + + // handle --help and --version + if (options.help) { + print_version(); + print_help(argv[0]); + return 0; + } + if (options.version) { + print_version(); + return 0; + } + + // initialize logger + switch (options.logger) { + case LOGGER_STDOUT: + BLog_InitStdout(); + break; + #ifndef BADVPN_USE_WINAPI + case LOGGER_SYSLOG: + if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) { + fprintf(stderr, "Failed to initialize syslog logger\n"); + goto fail0; + } + break; + #endif + default: + ASSERT(0); + } + + // configure logger channels + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + if (options.loglevels[i] >= 0) { + BLog_SetChannelLoglevel(i, options.loglevels[i]); + } + else if (options.loglevel >= 0) { + BLog_SetChannelLoglevel(i, options.loglevel); + } + } + + BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION); + + // initialize sockets + if (BSocket_GlobalInit() < 0) { + BLog(BLOG_ERROR, "BSocket_GlobalInit failed"); + goto fail1; + } + + // process arguments + if (!process_arguments()) { + BLog(BLOG_ERROR, "Failed to process arguments"); + goto fail1; + } + + // compute MTUs + if ((udpgw_mtu = udpgw_compute_mtu(options.udp_mtu)) < 0 || + udpgw_mtu > PACKETPROTO_MAXPAYLOAD + ) { + BLog(BLOG_ERROR, "MTU is too big"); + goto fail1; + } + pp_mtu = udpgw_mtu + sizeof(struct packetproto_header); + + // init time + BTime_Init(); + + // init reactor + if (!BReactor_Init(&ss)) { + BLog(BLOG_ERROR, "BReactor_Init failed"); + goto fail1; + } + + // setup signal handler + if (!BSignal_Init(&ss, signal_handler, NULL)) { + BLog(BLOG_ERROR, "BSignal_Init failed"); + goto fail2; + } + + // initialize listeners + num_listeners = 0; + while (num_listeners < num_listen_addrs) { + if (!Listener_Init(&listeners[num_listeners], &ss, listen_addrs[num_listeners], (Listener_handler)listener_handler, &listeners[num_listeners])) { + BLog(BLOG_ERROR, "Listener_Init failed"); + goto fail3; + } + num_listeners++; + } + + // init clients list + LinkedList1_Init(&clients_list); + num_clients = 0; + + // enter event loop + BLog(BLOG_NOTICE, "entering event loop"); + BReactor_Exec(&ss); + + // free clients + while (!LinkedList1_IsEmpty(&clients_list)) { + struct client *client = UPPER_OBJECT(LinkedList1_GetFirst(&clients_list), struct client, clients_list_node); + client_free(client); + } +fail3: + // free listeners + while (num_listeners > 0) { + num_listeners--; + Listener_Free(&listeners[num_listeners]); + } + // finish signal handling + BSignal_Finish(); +fail2: + // free reactor + BReactor_Free(&ss); +fail1: + // free logger + BLog(BLOG_NOTICE, "exiting"); + BLog_Free(); +fail0: + // finish debug objects + DebugObjectGlobal_Finish(); + + return 1; +} + +void print_help (const char *name) +{ + printf( + "Usage:\n" + " %s\n" + " [--help]\n" + " [--version]\n" + " [--logger <"LOGGERS_STRING">]\n" + #ifndef BADVPN_USE_WINAPI + " (logger=syslog?\n" + " [--syslog-facility <string>]\n" + " [--syslog-ident <string>]\n" + " )\n" + #endif + " [--loglevel <0-5/none/error/warning/notice/info/debug>]\n" + " [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n" + " [--listen-addr <addr>] ...\n" + " [--udp-mtu <bytes>]\n" + " [--max-clients <number>]\n" + " [--max-connections-for-client <number>]\n" + "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n", + name + ); +} + +void print_version (void) +{ + printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n"); +} + +int parse_arguments (int argc, char *argv[]) +{ + if (argc <= 0) { + return 0; + } + + options.help = 0; + options.version = 0; + options.logger = LOGGER_STDOUT; + #ifndef BADVPN_USE_WINAPI + options.logger_syslog_facility = "daemon"; + options.logger_syslog_ident = argv[0]; + #endif + options.loglevel = -1; + for (int i = 0; i < BLOG_NUM_CHANNELS; i++) { + options.loglevels[i] = -1; + } + options.num_listen_addrs = 0; + options.udp_mtu = DEFAULT_UDP_MTU; + options.max_clients = DEFAULT_MAX_CLIENTS; + options.max_connections_for_client = DEFAULT_MAX_CONNECTIONS_FOR_CLIENT; + + int i; + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (!strcmp(arg, "--help")) { + options.help = 1; + } + else if (!strcmp(arg, "--version")) { + options.version = 1; + } + else if (!strcmp(arg, "--logger")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + char *arg2 = argv[i + 1]; + if (!strcmp(arg2, "stdout")) { + options.logger = LOGGER_STDOUT; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg2, "syslog")) { + options.logger = LOGGER_SYSLOG; + } + #endif + else { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + #ifndef BADVPN_USE_WINAPI + else if (!strcmp(arg, "--syslog-facility")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_facility = argv[i + 1]; + i++; + } + else if (!strcmp(arg, "--syslog-ident")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + options.logger_syslog_ident = argv[i + 1]; + i++; + } + #endif + else if (!strcmp(arg, "--loglevel")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--channel-loglevel")) { + if (2 >= argc - i) { + fprintf(stderr, "%s: requires two arguments\n", arg); + return 0; + } + int channel = BLogGlobal_GetChannelByName(argv[i + 1]); + if (channel < 0) { + fprintf(stderr, "%s: wrong channel argument\n", arg); + return 0; + } + int loglevel = parse_loglevel(argv[i + 2]); + if (loglevel < 0) { + fprintf(stderr, "%s: wrong loglevel argument\n", arg); + return 0; + } + options.loglevels[channel] = loglevel; + i += 2; + } + else if (!strcmp(arg, "--listen-addr")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if (options.num_listen_addrs == MAX_LISTEN_ADDRS) { + fprintf(stderr, "%s: too many\n", arg); + return 0; + } + options.listen_addrs[options.num_listen_addrs] = argv[i + 1]; + options.num_listen_addrs++; + i++; + } + else if (!strcmp(arg, "--udp-mtu")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.udp_mtu = atoi(argv[i + 1])) < 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--max-clients")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.max_clients = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else if (!strcmp(arg, "--max-connections-for-client")) { + if (1 >= argc - i) { + fprintf(stderr, "%s: requires an argument\n", arg); + return 0; + } + if ((options.max_connections_for_client = atoi(argv[i + 1])) <= 0) { + fprintf(stderr, "%s: wrong argument\n", arg); + return 0; + } + i++; + } + else { + fprintf(stderr, "unknown option: %s\n", arg); + return 0; + } + } + + if (options.help || options.version) { + return 1; + } + + return 1; +} + +int process_arguments (void) +{ + // resolve listen addresses + num_listen_addrs = 0; + while (num_listen_addrs < options.num_listen_addrs) { + if (!BAddr_Parse(&listen_addrs[num_listen_addrs], options.listen_addrs[num_listen_addrs], NULL, 0)) { + BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed"); + return 0; + } + num_listen_addrs++; + } + + return 1; +} + +void signal_handler (void *unused) +{ + BLog(BLOG_NOTICE, "termination requested"); + + // exit event loop + BReactor_Quit(&ss, 1); +} + +void listener_handler (Listener *listener) +{ + if (num_clients == options.max_clients) { + BLog(BLOG_ERROR, "maximum number of clients reached"); + goto fail0; + } + + // allocate structure + struct client *client = malloc(sizeof(*client)); + if (!client) { + BLog(BLOG_ERROR, "malloc failed"); + goto fail0; + } + + // accept client + if (!Listener_Accept(listener, &client->sock, &client->addr)) { + BLog(BLOG_ERROR, "Listener_Accept failed"); + goto fail1; + } + + // limit socket send buffer, else our scheduling is pointless + if (BSocket_SetSendBuffer(&client->sock, CLIENT_SOCKET_SEND_BUFFER) < 0) { + BLog(BLOG_WARNING, "BSocket_SetSendBuffer failed"); + } + + // init disconnect timer + BTimer_Init(&client->disconnect_timer, CLIENT_DISCONNECT_TIMEOUT, (BTimer_handler)client_disconnect_timer_handler, client); + BReactor_SetTimer(&ss, &client->disconnect_timer); + + // init error domain + FlowErrorDomain_Init(&client->domain, (FlowErrorDomain_handler)client_error_handler, client); + + // init recv source + StreamSocketSource_Init(&client->recv_source, FlowErrorReporter_Create(&client->domain, 0), &client->sock, BReactor_PendingGroup(&ss)); + + // init recv interface + PacketPassInterface_Init(&client->recv_if, udpgw_mtu, (PacketPassInterface_handler_send)client_recv_if_handler_send, client, BReactor_PendingGroup(&ss)); + + // init recv decoder + if (!PacketProtoDecoder_Init(&client->recv_decoder, FlowErrorReporter_Create(&client->domain, 0), StreamSocketSource_GetOutput(&client->recv_source), &client->recv_if, BReactor_PendingGroup(&ss))) { + BLog(BLOG_ERROR, "PacketProtoDecoder_Init failed"); + goto fail2; + } + + // init send sink + StreamSocketSink_Init(&client->send_sink, FlowErrorReporter_Create(&client->domain, 0), &client->sock, BReactor_PendingGroup(&ss)); + + // init send sender + PacketStreamSender_Init(&client->send_sender, StreamSocketSink_GetInput(&client->send_sink), pp_mtu, BReactor_PendingGroup(&ss)); + + // init send queue + PacketPassFairQueue_Init(&client->send_queue, PacketStreamSender_GetInput(&client->send_sender), BReactor_PendingGroup(&ss), 0, 1); + + // init connections tree + BAVL_Init(&client->connections_tree, OFFSET_DIFF(struct connection, conid, connections_tree_node), (BAVL_comparator)uint16_comparator, NULL); + + // init connections list + LinkedList1_Init(&client->connections_list); + + // set zero connections + client->num_connections = 0; + + // init closing connections list + LinkedList1_Init(&client->closing_connections_list); + + // insert to clients list + LinkedList1_Append(&clients_list, &client->clients_list_node); + num_clients++; + + client_log(client, BLOG_INFO, "connected"); + + return; + +fail2: + PacketPassInterface_Free(&client->recv_if); + StreamSocketSource_Free(&client->recv_source); + BReactor_RemoveTimer(&ss, &client->disconnect_timer); + BSocket_Free(&client->sock); +fail1: + free(client); +fail0: + return; +} + +void client_free (struct client *client) +{ + // allow freeing send queue flows + PacketPassFairQueue_PrepareFree(&client->send_queue); + + // free connections + while (!LinkedList1_IsEmpty(&client->connections_list)) { + struct connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&client->connections_list), struct connection, connections_list_node); + connection_free(con); + } + + // free closing connections + while (!LinkedList1_IsEmpty(&client->closing_connections_list)) { + struct connection *con = UPPER_OBJECT(LinkedList1_GetFirst(&client->closing_connections_list), struct connection, closing_connections_list_node); + connection_free(con); + } + + // remove from clients list + LinkedList1_Remove(&clients_list, &client->clients_list_node); + num_clients--; + + // free send queue + PacketPassFairQueue_Free(&client->send_queue); + + // free send sender + PacketStreamSender_Free(&client->send_sender); + + // free send sink + StreamSocketSink_Free(&client->send_sink); + + // free recv decoder + PacketProtoDecoder_Free(&client->recv_decoder); + + // free recv interface + PacketPassInterface_Free(&client->recv_if); + + // free recv source + StreamSocketSource_Free(&client->recv_source); + + // free disconnect timer + BReactor_RemoveTimer(&ss, &client->disconnect_timer); + + // free socket + BSocket_Free(&client->sock); + + // free structure + free(client); +} + +void client_log (struct client *client, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + char addr[BADDR_MAX_PRINT_LEN]; + BAddr_Print(&client->addr, addr); + BLog_Append("client (%s): ", addr); + BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +void client_disconnect_timer_handler (struct client *client) +{ + client_log(client, BLOG_INFO, "timed out, disconnecting"); + + // free client + client_free(client); +} + +void client_error_handler (struct client *client, int component, int code) +{ + client_log(client, BLOG_INFO, "error"); + + // free client + client_free(client); +} + +void client_recv_if_handler_send (struct client *client, uint8_t *data, int data_len) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= udpgw_mtu) + + // accept packet + PacketPassInterface_Done(&client->recv_if); + + // parse header + if (data_len < sizeof(struct udpgw_header)) { + client_log(client, BLOG_ERROR, "missing header"); + return; + } + struct udpgw_header *header = (struct udpgw_header *)data; + data += sizeof(*header); + data_len -= sizeof(*header); + uint8_t flags = ltoh8(header->flags); + uint16_t conid = ltoh16(header->conid); + + // reset disconnect timer + BReactor_SetTimer(&ss, &client->disconnect_timer); + + // if this is keepalive, ignore any payload + if ((flags & UDPGW_CLIENT_FLAG_KEEPALIVE)) { + client_log(client, BLOG_DEBUG, "received keepalive"); + return; + } + + // check payload length + if (data_len > options.udp_mtu) { + client_log(client, BLOG_ERROR, "too much data"); + return; + } + + // find connection + struct connection *con = find_connection(client, conid); + ASSERT(!con || !con->closing) + + // if connection exists, close it if needed + if (con && ((flags & UDPGW_CLIENT_FLAG_REBIND) || con->addr.ipv4.ip != header->addr_ip || con->addr.ipv4.port != header->addr_port)) { + connection_log(con, BLOG_DEBUG, "close old"); + + connection_close(con); + con = NULL; + } + + // if connection doesn't exists, create it + if (!con) { + // check number of connections + if (client->num_connections == options.max_connections_for_client) { + // close least recently used connection + con = UPPER_OBJECT(LinkedList1_GetFirst(&client->connections_list), struct connection, connections_list_node); + connection_close(con); + } + + // read address + BAddr addr; + BAddr_InitIPv4(&addr, header->addr_ip, header->addr_port); + + // create new connection + connection_init(client, conid, addr, data, data_len); + } else { + // submit packet to existing connection + connection_send_to_udp(con, data, data_len); + } +} + +void connection_init (struct client *client, uint16_t conid, BAddr addr, const uint8_t *data, int data_len) +{ + ASSERT(client->num_connections < options.max_connections_for_client) + ASSERT(!find_connection(client, conid)) + BAddr_Assert(&addr); + ASSERT(addr.type == BADDR_TYPE_IPV4) + ASSERT(data_len >= 0) + ASSERT(data_len <= options.udp_mtu) + + // allocate structure + struct connection *con = malloc(sizeof(*con)); + if (!con) { + client_log(client, BLOG_ERROR, "malloc failed"); + goto fail0; + } + + // init arguments + con->client = client; + con->conid = conid; + con->addr = addr; + con->first_data = data; + con->first_data_len = data_len; + + // set not closing + con->closing = 0; + + // init first job + BPending_Init(&con->first_job, BReactor_PendingGroup(&ss), (BPending_handler)connection_first_job_handler, con); + BPending_Set(&con->first_job); + + // init send queue flow + PacketPassFairQueueFlow_Init(&con->send_qflow, &client->send_queue); + + // init send PacketProtoFlow + if (!PacketProtoFlow_Init(&con->send_ppflow, udpgw_mtu, CONNECTION_CLIENT_BUFFER_SIZE, PacketPassFairQueueFlow_GetInput(&con->send_qflow), BReactor_PendingGroup(&ss))) { + client_log(client, BLOG_ERROR, "PacketProtoFlow_Init failed"); + goto fail1; + } + con->send_if = PacketProtoFlow_GetInput(&con->send_ppflow); + + // init UDP socket + if (BSocket_Init(&con->udp_sock, &ss, addr.type, BSOCKET_TYPE_DGRAM) < 0) { + client_log(client, BLOG_ERROR, "BSocket_Init failed"); + goto fail2; + } + + // connect the socket + // Windows needs this or receive will fail; however, FreeBSD will refuse to send + // if this is done + #ifdef BADVPN_USE_WINAPI + if (BSocket_Connect(&con->udp_sock, &addr, 0) < 0) { + client_log(client, BLOG_ERROR, "BSocket_Connect failed"); + goto fail3; + } + #endif + + // init UDP error domain + FlowErrorDomain_Init(&con->udp_domain, (FlowErrorDomain_handler)connection_udp_error_handler, con); + + // init UDP sink + BIPAddr ipaddr; + BIPAddr_InitInvalid(&ipaddr); + DatagramSocketSink_Init(&con->udp_send_sink, FlowErrorReporter_Create(&con->udp_domain, 0), &con->udp_sock, options.udp_mtu, addr, ipaddr, BReactor_PendingGroup(&ss)); + + // init UDP writer + BufferWriter_Init(&con->udp_send_writer, options.udp_mtu, BReactor_PendingGroup(&ss)); + + // init UDP buffer + if (!PacketBuffer_Init(&con->udp_send_buffer, BufferWriter_GetOutput(&con->udp_send_writer), DatagramSocketSink_GetInput(&con->udp_send_sink), CONNECTION_UDP_BUFFER_SIZE, BReactor_PendingGroup(&ss))) { + client_log(client, BLOG_ERROR, "PacketBuffer_Init failed"); + goto fail4; + } + + // init UDP receive source + DatagramSocketSource_Init(&con->udp_recv_source, FlowErrorReporter_Create(&con->udp_domain, 0), &con->udp_sock, options.udp_mtu, BReactor_PendingGroup(&ss)); + + // init UDP recv interface + PacketPassInterface_Init(&con->udp_recv_if, options.udp_mtu, (PacketPassInterface_handler_send)connection_udp_recv_if_handler_send, con, BReactor_PendingGroup(&ss)); + + // init UDP recv buffer + if (!SinglePacketBuffer_Init(&con->udp_recv_buffer, DatagramSocketSource_GetOutput(&con->udp_recv_source), &con->udp_recv_if, BReactor_PendingGroup(&ss))) { + client_log(client, BLOG_ERROR, "SinglePacketBuffer_Init failed"); + goto fail5; + } + + // insert to client's connections tree + ASSERT_EXECUTE(BAVL_Insert(&client->connections_tree, &con->connections_tree_node, NULL)) + + // insert to client's connections list + LinkedList1_Append(&client->connections_list, &con->connections_list_node); + + // increment number of connections + client->num_connections++; + + connection_log(con, BLOG_DEBUG, "initialized"); + + return; + +fail5: + PacketPassInterface_Free(&con->udp_recv_if); + DatagramSocketSource_Free(&con->udp_recv_source); + PacketBuffer_Free(&con->udp_send_buffer); +fail4: + BufferWriter_Free(&con->udp_send_writer); + DatagramSocketSink_Free(&con->udp_send_sink); +fail3: + BSocket_Free(&con->udp_sock); +fail2: + PacketProtoFlow_Free(&con->send_ppflow); +fail1: + PacketPassFairQueueFlow_Free(&con->send_qflow); + BPending_Free(&con->first_job); + free(con); +fail0: + return; +} + +void connection_free (struct connection *con) +{ + struct client *client = con->client; + PacketPassFairQueueFlow_AssertFree(&con->send_qflow); + + if (con->closing) { + // remove from client's closing connections list + LinkedList1_Remove(&client->closing_connections_list, &con->closing_connections_list_node); + } else { + // decrement number of connections + client->num_connections--; + + // remove from client's connections list + LinkedList1_Remove(&client->connections_list, &con->connections_list_node); + + // remove from client's connections tree + BAVL_Remove(&client->connections_tree, &con->connections_tree_node); + + // free UDP + connection_free_udp(con); + } + + // free send PacketProtoFlow + PacketProtoFlow_Free(&con->send_ppflow); + + // free send queue flow + PacketPassFairQueueFlow_Free(&con->send_qflow); + + // free first job + BPending_Free(&con->first_job); + + // free structure + free(con); +} + +void connection_log (struct connection *con, int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + char addr[BADDR_MAX_PRINT_LEN]; + BAddr_Print(&con->client->addr, addr); + if (con->closing) { + BLog_Append("client (%s): old connection %"PRIu16": ", addr, con->conid); + } else { + BLog_Append("client (%s): connection %"PRIu16": ", addr, con->conid); + } + BLog_LogToChannelVarArg(BLOG_CURRENT_CHANNEL, level, fmt, vl); + va_end(vl); +} + +void connection_free_udp (struct connection *con) +{ + // free UDP receive buffer + SinglePacketBuffer_Free(&con->udp_recv_buffer); + + // free UDP receive interface + PacketPassInterface_Free(&con->udp_recv_if); + + // free UDP receive source + DatagramSocketSource_Free(&con->udp_recv_source); + + // free UDP buffer + PacketBuffer_Free(&con->udp_send_buffer); + + // free UDP writer + BufferWriter_Free(&con->udp_send_writer); + + // free UDP sink + DatagramSocketSink_Free(&con->udp_send_sink); + + // free UDP socket + BSocket_Free(&con->udp_sock); +} + +void connection_first_job_handler (struct connection *con) +{ + ASSERT(!con->closing) + + connection_send_to_udp(con, con->first_data, con->first_data_len); +} + +int connection_send_to_client (struct connection *con, uint8_t flags, const uint8_t *data, int data_len) +{ + ASSERT(data_len >= 0) + ASSERT(data_len <= options.udp_mtu) + + // get buffer location + uint8_t *out; + if (!BufferWriter_StartPacket(con->send_if, &out)) { + connection_log(con, BLOG_ERROR, "out of client buffer"); + return 0; + } + + // write header + struct udpgw_header *header = (struct udpgw_header *)out; + header->flags = htol8(flags); + header->conid = htol16(con->conid); + header->addr_ip = con->addr.ipv4.ip; + header->addr_port = con->addr.ipv4.port; + + // write message + memcpy(out + sizeof(*header), data, data_len); + + // submit written message + BufferWriter_EndPacket(con->send_if, sizeof(*header) + data_len); + + return 1; +} + +int connection_send_to_udp (struct connection *con, const uint8_t *data, int data_len) +{ + struct client *client = con->client; + ASSERT(!con->closing) + ASSERT(data_len >= 0) + ASSERT(data_len <= options.udp_mtu) + + connection_log(con, BLOG_DEBUG, "from client %d bytes", data_len); + + // move connection to front + LinkedList1_Remove(&client->connections_list, &con->connections_list_node); + LinkedList1_Append(&client->connections_list, &con->connections_list_node); + + // get buffer location + uint8_t *out; + if (!BufferWriter_StartPacket(&con->udp_send_writer, &out)) { + connection_log(con, BLOG_ERROR, "out of UDP buffer"); + return 0; + } + + // write message + memcpy(out, data, data_len); + + // submit written message + BufferWriter_EndPacket(&con->udp_send_writer, data_len); + + return 1; +} + +void connection_close (struct connection *con) +{ + struct client *client = con->client; + ASSERT(!con->closing) + + // if possible, free connection immediately + if (!PacketPassFairQueueFlow_IsBusy(&con->send_qflow)) { + connection_free(con); + return; + } + + connection_log(con, BLOG_DEBUG, "closing later"); + + // decrement number of connections + client->num_connections--; + + // remove from client's connections list + LinkedList1_Remove(&client->connections_list, &con->connections_list_node); + + // remove from client's connections tree + BAVL_Remove(&client->connections_tree, &con->connections_tree_node); + + // free UDP + connection_free_udp(con); + + // insert to client's closing connections list + LinkedList1_Append(&client->closing_connections_list, &con->closing_connections_list_node); + + // set busy handler + PacketPassFairQueueFlow_SetBusyHandler(&con->send_qflow, (PacketPassFairQueue_handler_busy)connection_send_qflow_busy_handler, con); + + // unset first job + BPending_Unset(&con->first_job); + + // set closing + con->closing = 1; +} + +void connection_send_qflow_busy_handler (struct connection *con) +{ + ASSERT(con->closing) + PacketPassFairQueueFlow_AssertFree(&con->send_qflow); + + connection_log(con, BLOG_DEBUG, "closing finally"); + + // free connection + connection_free(con); +} + +void connection_udp_error_handler (struct connection *con, int component, int code) +{ + ASSERT(!con->closing) + + connection_log(con, BLOG_INFO, "UDP error"); + + // close connection + connection_close(con); +} + +void connection_udp_recv_if_handler_send (struct connection *con, uint8_t *data, int data_len) +{ + struct client *client = con->client; + ASSERT(!con->closing) + ASSERT(data_len >= 0) + ASSERT(data_len <= options.udp_mtu) + + connection_log(con, BLOG_DEBUG, "from UDP %d bytes", data_len); + + // move connection to front + LinkedList1_Remove(&client->connections_list, &con->connections_list_node); + LinkedList1_Append(&client->connections_list, &con->connections_list_node); + + // accept packet + PacketPassInterface_Done(&con->udp_recv_if); + + // send packet to client + connection_send_to_client(con, 0, data, data_len); +} + +struct connection * find_connection (struct client *client, uint16_t conid) +{ + BAVLNode *tree_node = BAVL_LookupExact(&client->connections_tree, &conid); + if (!tree_node) { + return NULL; + } + struct connection *con = UPPER_OBJECT(tree_node, struct connection, connections_tree_node); + ASSERT(con->conid == conid) + ASSERT(!con->closing) + + return con; +} + +int uint16_comparator (void *unused, uint16_t *v1, uint16_t *v2) +{ + if (*v1 < *v2) { + return -1; + } + if (*v1 > *v2) { + return 1; + } + return 0; +} diff --git a/udpgw/udpgw.h b/udpgw/udpgw.h new file mode 100644 index 0000000..f933bee --- /dev/null +++ b/udpgw/udpgw.h @@ -0,0 +1,48 @@ +/** + * @file udpgw.h + * @author Ambroz Bizjak <ambrop7@gmail.com> + * + * @section LICENSE + * + * This file is part of BadVPN. + * + * BadVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * BadVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// name of the program +#define PROGRAM_NAME "udpgw" + +// maxiumum listen addresses +#define MAX_LISTEN_ADDRS 16 + +// maximum datagram size +#define DEFAULT_UDP_MTU 65520 + +// connection buffer size for sending to client, in packets +#define CONNECTION_CLIENT_BUFFER_SIZE 1 + +// connection buffer size for sending to UDP, in packets +#define CONNECTION_UDP_BUFFER_SIZE 1 + +// maximum number of clients +#define DEFAULT_MAX_CLIENTS 4 + +// maximum connections for client +#define DEFAULT_MAX_CONNECTIONS_FOR_CLIENT 512 + +// how long after nothing has been received to disconnect a client +#define CLIENT_DISCONNECT_TIMEOUT 20000 + +// SO_SNDBFUF socket option for clients +#define CLIENT_SOCKET_SEND_BUFFER 4096 |