From 16add85eb81629b07dd7bdb04fbe30be1410cc83 Mon Sep 17 00:00:00 2001 From: GDR! Date: Tue, 25 Nov 2014 23:09:45 +0100 Subject: Multi-connection support client-side. Dealing with congestion control, too --- client.c | 221 ++++++++++++++++++++++++++++++++++++++++----------------------- client.h | 1 + main.c | 78 ++++++++++++++++++---- main.h | 8 ++- 4 files changed, 213 insertions(+), 95 deletions(-) diff --git a/client.c b/client.c index 190c7e5..75d4b0d 100644 --- a/client.c +++ b/client.c @@ -10,6 +10,9 @@ struct timespec ping_sent_time; /* Client mode tunnel */ tunnel client_tunnel; +/* Sock representing the local port - call accept() on it */ +int bind_sockfd; + fd_set client_master_fdset; int handle_pong_frame(protocol_frame *rcvd_frame) @@ -31,17 +34,12 @@ int handle_pong_frame(protocol_frame *rcvd_frame) } } -int local_bind(tunnel *tun) +int local_bind() { struct addrinfo hints, *res; - int sockfd; char port[6]; int yes = 1; - - /* accept() variables - TODO they should not be there */ - int newfd; - struct sockaddr_storage remoteaddr; // client address - socklen_t addrlen; + int flags; snprintf(port, 6, "%d", local_port); @@ -52,69 +50,66 @@ int local_bind(tunnel *tun) getaddrinfo(NULL, port, &hints, &res); - sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if(sockfd < 0) + bind_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if(bind_sockfd < 0) { - fprintf(stderr, "Could not create a socket for local listening\n"); + fprintf(stderr, "Could not create a socket for local listening: %s\n", strerror(errno)); exit(1); } - setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); + setsockopt(bind_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); - if(bind(sockfd, res->ai_addr, res->ai_addrlen) < 0) + /* Set O_NONBLOCK to make accept() non-blocking */ + if (-1 == (flags = fcntl(bind_sockfd, F_GETFL, 0))) { - fprintf(stderr, "Bind to port %d failed: %s", local_port, strerror(errno)); - close(sockfd); - exit(1); + flags = 0; } + fcntl(bind_sockfd, F_SETFL, flags | O_NONBLOCK); - if(listen(sockfd, 1) < 0) + if(bind(bind_sockfd, res->ai_addr, res->ai_addrlen) < 0) { - fprintf(stderr, "Listening on port %d failed: %s", local_port, strerror(errno)); - close(sockfd); + fprintf(stderr, "Bind to port %d failed: %s\n", local_port, strerror(errno)); + close(bind_sockfd); exit(1); } - // TODO return sockfd - - /* TODO: make a proper accept loop and track tunnels, to handle more than 1 connection */ - addrlen = sizeof(remoteaddr); - newfd = accept(sockfd, - (struct sockaddr *)&remoteaddr, - &addrlen); - - if(newfd < 0) + if(listen(bind_sockfd, 1) < 0) { - fprintf(stderr, "Error when accepting a local connection: %s\n", strerror(errno)); - close(sockfd); - exit(0); + fprintf(stderr, "Listening on port %d failed: %s\n", local_port, strerror(errno)); + close(bind_sockfd); + exit(1); } -// TODO close(sockfd); - - return newfd; + fprintf(stderr, "Bound to local port %d\n", local_port); } +/* Bind the client.sockfd to a tunnel */ int handle_acktunnel_frame(protocol_frame *rcvd_frame) { + tunnel *tun; + if(!client_mode) { fprintf(stderr, "Got ACK tunnel frame when not in client mode!?\n"); return -1; } - client_tunnel.connid = rcvd_frame->connid; - client_tunnel.friendnumber = rcvd_frame->friendnumber; - // TODO open local port and fill client_tunnel.sockfd - printf("New tunnel ID: %d\n", client_tunnel.connid); + tun = tunnel_create( + client_tunnel.sockfd, + rcvd_frame->connid, + rcvd_frame->friendnumber + ); + + /* Mark that we can accept() another connection */ + client_tunnel.sockfd = -1; + + printf("New tunnel ID: %d\n", tun->connid); if(client_local_port_mode) { - client_tunnel.sockfd = local_bind(&client_tunnel); - update_select_nfds(client_tunnel.sockfd); - FD_SET(client_tunnel.sockfd, &client_master_fdset); - fprintf(stderr, "Accepting connections on port %d\n", local_port); - state = CLIENT_STATE_FORWARDING; + update_select_nfds(tun->sockfd); + FD_SET(tun->sockfd, &client_master_fdset); + fprintf(stderr, "Accepted a new connection on port %d\n", local_port); } else { @@ -126,9 +121,17 @@ int handle_acktunnel_frame(protocol_frame *rcvd_frame) /* Handle a TCP frame received from server */ int handle_server_tcp_frame(protocol_frame *rcvd_frame) { - /* TODO find tunnel basing on ID */ int offset = 0; - tunnel *tun = &client_tunnel; + tunnel *tun = NULL; + int tun_id = rcvd_frame->connid; + + HASH_FIND_INT(by_id, &tun_id, tun); + + if(!tun) + { + fprintf(stderr, "Got TCP frame with unknown tunnel ID %d\n", rcvd_frame->connid); + return -1; + } while(offset < rcvd_frame->data_length) { @@ -143,13 +146,27 @@ int handle_server_tcp_frame(protocol_frame *rcvd_frame) if(sent_bytes < 0) { + char data[PROTOCOL_BUFFER_OFFSET]; + protocol_frame frame_st, *frame; + fprintf(stderr, "Could not write to socket %d: %s\n", tun->sockfd, strerror(errno)); + + frame = &frame_st; + memset(frame, 0, sizeof(protocol_frame)); + frame->friendnumber = tun->friendnumber; + frame->packet_type = PACKET_TYPE_TCP_FIN; + frame->connid = tun->connid; + frame->data_length = 0; + send_frame(frame, data); + tunnel_delete(tun); + return -1; } offset += sent_bytes; } + printf("Got %d bytes from server - wrote to fd %d\n", rcvd_frame->data_length, tun->sockfd); return 0; } @@ -172,6 +189,12 @@ int do_client_loop(char *tox_id_str) exit(1); } + if(!ping_mode) /* TODO handle pipe mode */ + { + local_bind(); + signal(SIGPIPE, SIG_IGN); + } + fprintf(stderr, "Connecting to Tox...\n"); while(1) @@ -232,7 +255,7 @@ int do_client_loop(char *tox_id_str) } else { - state = CLIENT_STATE_REQUEST_TUNNEL; + state = CLIENT_STATE_BIND_PORT; } break; case CLIENT_STATE_SEND_PING: @@ -256,9 +279,21 @@ int do_client_loop(char *tox_id_str) case CLIENT_STATE_PING_SENT: /* Just sit there and wait for pong */ break; + + case CLIENT_STATE_BIND_PORT: + if(bind_sockfd < 0) + { + fprintf(stderr, "Shutting down - could not bind to listening port\n"); + state = CLIENT_STATE_SHUTDOWN; + } + else + { + state = CLIENT_STATE_FORWARDING; + } + break; case CLIENT_STATE_REQUEST_TUNNEL: send_tunnel_request_packet( - "127.0.0.1", + remote_host, remote_port, friendnumber ); @@ -268,49 +303,73 @@ int do_client_loop(char *tox_id_str) break; case CLIENT_STATE_FORWARDING: { + int accept_fd = 0; + tunnel *tmp = NULL; + tunnel *tun = NULL; + tv.tv_sec = 0; tv.tv_usec = 20000; fds = client_master_fdset; - - select(select_nfds, &fds, NULL, NULL, &tv); - if(FD_ISSET(client_tunnel.sockfd, &fds)) + /* Handle accepting new connections */ + if(client_tunnel.sockfd <= 0) /* Don't accept if we're already waiting to establish a tunnel */ { - int nbytes = recv(client_tunnel.sockfd, - tox_packet_buf + PROTOCOL_BUFFER_OFFSET, - READ_BUFFER_SIZE, 0); - - /* Check if connection closed */ - if(nbytes == 0) + accept_fd = accept(bind_sockfd, NULL, NULL); + if(accept_fd != -1) { - char data[PROTOCOL_BUFFER_OFFSET]; - protocol_frame frame_st, *frame; - - fprintf(stderr, "Connection closed\n"); - - frame = &frame_st; - memset(frame, 0, sizeof(protocol_frame)); - frame->friendnumber = client_tunnel.friendnumber; - frame->packet_type = PACKET_TYPE_TCP_FIN; - frame->connid = client_tunnel.connid; - frame->data_length = 0; - send_frame(frame, data); - - state = CLIENT_STATE_SHUTDOWN; - -// exit(1); // TODO handle it in a smarter way (accept() again?) + fprintf(stderr, "Accepting a new connection - requesting tunnel...\n"); + + /* Open a new tunnel for this FD */ + client_tunnel.sockfd = accept_fd; + send_tunnel_request_packet( + remote_host, + remote_port, + friendnumber + ); } - else + } + + /* Handle reading from sockets */ + select(select_nfds, &fds, NULL, NULL, &tv); + HASH_ITER(hh, by_id, tun, tmp) + { + if(FD_ISSET(tun->sockfd, &fds)) { - protocol_frame frame_st, *frame; - - frame = &frame_st; - memset(frame, 0, sizeof(protocol_frame)); - frame->friendnumber = client_tunnel.friendnumber; - frame->packet_type = PACKET_TYPE_TCP; - frame->connid = client_tunnel.connid; - frame->data_length = nbytes; - send_frame(frame, tox_packet_buf); + int nbytes = recv(tun->sockfd, + tox_packet_buf + PROTOCOL_BUFFER_OFFSET, + READ_BUFFER_SIZE, 0); + + /* Check if connection closed */ + if(nbytes == 0) + { + char data[PROTOCOL_BUFFER_OFFSET]; + protocol_frame frame_st, *frame; + + fprintf(stderr, "Connection closed\n"); + + frame = &frame_st; + memset(frame, 0, sizeof(protocol_frame)); + frame->friendnumber = tun->friendnumber; + frame->packet_type = PACKET_TYPE_TCP_FIN; + frame->connid = tun->connid; + frame->data_length = 0; + send_frame(frame, data); + tunnel_delete(tun); + } + else + { + protocol_frame frame_st, *frame; + + frame = &frame_st; + memset(frame, 0, sizeof(protocol_frame)); + frame->friendnumber = tun->friendnumber; + frame->packet_type = PACKET_TYPE_TCP; + frame->connid = tun->connid; + frame->data_length = nbytes; + send_frame(frame, tox_packet_buf); + + printf("Wrote %d bytes from sock %d to tunnel %d\n", nbytes, tun->sockfd, tun->connid); + } } } diff --git a/client.h b/client.h index bcbb205..7d5e50d 100644 --- a/client.h +++ b/client.h @@ -11,6 +11,7 @@ #define CLIENT_STATE_WAIT_FOR_ACKTUNNEL 9 #define CLIENT_STATE_FORWARDING 10 #define CLIENT_STATE_SHUTDOWN 11 +#define CLIENT_STATE_BIND_PORT 12 int handle_pong_frame(protocol_frame *rcvd_frame); int handle_acktunnel_frame(protocol_frame *rcvd_frame); diff --git a/main.c b/main.c index 77ef128..c3444ee 100644 --- a/main.c +++ b/main.c @@ -9,14 +9,19 @@ int client_socket = 0; /** CONFIGURATION OPTIONS **/ /* Whether we're a client */ int client_mode = 0; + /* Just send a ping and exit */ int ping_mode = 0; + /* Open a local port and forward it */ int client_local_port_mode = 0; + /* Forward stdin/stdout to remote machine - SSH ProxyCommand mode */ int client_pipe_mode = 0; + /* Remote Tox ID in client mode */ char *remote_tox_id = NULL; + /* Ports and hostname for port forwarding */ int remote_port = 0; char *remote_host = NULL; @@ -26,7 +31,6 @@ fd_set master_server_fds; /* We keep two hash tables: one indexed by sockfd and another by "connection id" */ tunnel *by_id = NULL; -tunnel *by_fd = NULL; /* Highest used fd + 1 for select() */ int select_nfds = 4; @@ -62,7 +66,7 @@ void update_select_nfds(int fd) } /* Constructor. Returns NULL on failure. */ -static tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber) +tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber) { tunnel *t = NULL; @@ -76,6 +80,8 @@ static tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber) t->connid = connid; t->friendnumber = friendnumber; + fprintf(stderr, "Created a new tunnel object connid=%d sockfd=%d\n", connid, sockfd); + update_select_nfds(t->sockfd); HASH_ADD_INT( by_id, connid, t ); @@ -83,7 +89,7 @@ static tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber) return t; } -static void tunnel_delete(tunnel *t) +void tunnel_delete(tunnel *t) { printf("Deleting tunnel #%d\n", t->connid); if(t->sockfd) @@ -167,7 +173,7 @@ int get_client_socket(char *hostname, int port) snprintf(port_str, 6, "%d", port); memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; + hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(hostname, port_str, &hints, &servinfo)) != 0) { @@ -177,6 +183,9 @@ int get_client_socket(char *hostname, int port) // loop through all the results and connect to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { + if (p->ai_family != AF_INET && p->ai_family != AF_INET6) + continue; + if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("client: socket"); @@ -216,7 +225,8 @@ int get_client_socket(char *hostname, int port) */ int send_frame(protocol_frame *frame, uint8_t *data) { - int rv; + int rv = -1; + int i; data[0] = PROTOCOL_MAGIC_HIGH; data[1] = PROTOCOL_MAGIC_LOW; @@ -227,16 +237,40 @@ int send_frame(protocol_frame *frame, uint8_t *data) data[6] = BYTE2(frame->data_length); data[7] = BYTE1(frame->data_length); - rv = tox_send_lossless_packet( - tox, - frame->friendnumber, - data, - frame->data_length + PROTOCOL_BUFFER_OFFSET - ); + for(i = 0; i < 17;) + { + int j; + + rv = tox_send_lossless_packet( + tox, + frame->friendnumber, + data, + frame->data_length + PROTOCOL_BUFFER_OFFSET + ); - if(rv < 0) + if(rv < 0) + { + /* If this branch is ran, most likely we've hit congestion control. */ + fprintf(stderr, "[%d] Failed to send packet to friend %d\n", i, frame->friendnumber); + } + else + { + break; + } + + if(i == 0) i = 2; + else i = i * 2; + + for(j = 0; j < i; j++) + { + tox_do(tox); + usleep(j * 10000); + } + } + + if(i > 0 && rv >= 0) { - fprintf(stderr, "Failed to send packet to friend %d\n", frame->friendnumber); + fprintf(stderr, "Packet succeeded at try %d", i+1); } return rv; @@ -570,6 +604,7 @@ int do_server_loop() unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; tunnel *tun = NULL; tunnel *tmp = NULL; + int connected = 0; tv.tv_sec = 0; tv.tv_usec = 20000; @@ -578,9 +613,26 @@ int do_server_loop() while(1) { + int tmp_isconnected = 0; + /* Let tox do its stuff */ tox_do(tox); + /* Check change in connection state */ + tmp_isconnected = tox_isconnected(tox); + if(tmp_isconnected != connected) + { + connected = tmp_isconnected; + if(connected) + { + fprintf(stderr, "Connected to Tox network\n"); + } + else + { + fprintf(stderr, "Disconnected from Tox network\n"); + } + } + fds = master_server_fds; /* Poll for data from our client connection */ diff --git a/main.h b/main.h index 4f2187d..ba0a6d0 100644 --- a/main.h +++ b/main.h @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -18,7 +20,6 @@ #include "util.h" #include "uthash.h" -#define READ_BUFFER_SIZE 1024 #define PROTOCOL_MAGIC_V1 0xa26a #define PROTOCOL_MAGIC PROTOCOL_MAGIC_V1 @@ -37,8 +38,10 @@ /* Offset of the data buffer in the packet */ #define PROTOCOL_BUFFER_OFFSET 8 +#define READ_BUFFER_SIZE TOX_MAX_CUSTOM_PACKET_SIZE - PROTOCOL_BUFFER_OFFSET #define PROTOCOL_MAX_PACKET_SIZE (READ_BUFFER_SIZE + PROTOCOL_BUFFER_OFFSET) + typedef struct tunnel_t { /* The forwarded socket fd */ int sockfd; @@ -80,6 +83,9 @@ extern char *remote_host; extern int local_port; extern int select_nfds; +extern tunnel *by_id; int parse_lossless_packet(void *sender_uc, const uint8_t *data, uint32_t len); +tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber); +void tunnel_delete(tunnel *t); #endif -- cgit v1.2.3