#include /* MacOS related */ #ifdef __MACH__ #include "mach.h" #endif #include "log.h" #include "main.h" #include "client.h" /* The state machine */ int state = CLIENT_STATE_INITIAL; /* Used in ping mode */ 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() { struct timespec pong_rcvd_time; double secs1, secs2; clock_gettime(CLOCK_MONOTONIC, &pong_rcvd_time); secs1 = (1.0 * ping_sent_time.tv_sec) + (1e-9 * ping_sent_time.tv_nsec); secs2 = (1.0 * pong_rcvd_time.tv_sec) + (1e-9 * pong_rcvd_time.tv_nsec); log_printf(L_INFO, "GOT PONG! Time = %.3fs\n", secs2-secs1); if(ping_mode) { state = CLIENT_STATE_SEND_PING; } return 0; } int local_bind() { struct addrinfo hints, *res; char port[6]; int yes = 1; int flags; int gai_status; int setsockopt_status; snprintf(port, 6, "%d", local_port); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // fill in my IP for me gai_status = getaddrinfo(NULL, port, &hints, &res); if(gai_status != 0) { log_printf(L_ERROR, "getaddrinfo: %s\n", gai_strerror(gai_status)); exit(1); } bind_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(bind_sockfd < 0) { log_printf(L_ERROR, "Could not create a socket for local listening: %s\n", strerror(errno)); freeaddrinfo(res); exit(1); } setsockopt_status = setsockopt(bind_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); if(setsockopt_status < 0) { log_printf(L_ERROR, "Could not set socket options: %s\n", strerror(errno)); freeaddrinfo(res); exit(1); } /* Set O_NONBLOCK to make accept() non-blocking */ if (-1 == (flags = fcntl(bind_sockfd, F_GETFL, 0))) { flags = 0; } if(fcntl(bind_sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { log_printf(L_ERROR, "Could not make the socket non-blocking: %s\n", strerror(errno)); freeaddrinfo(res); exit(1); } if(bind(bind_sockfd, res->ai_addr, res->ai_addrlen) < 0) { log_printf(L_ERROR, "Bind to port %d failed: %s\n", local_port, strerror(errno)); freeaddrinfo(res); close(bind_sockfd); exit(1); } freeaddrinfo(res); if(listen(bind_sockfd, 1) < 0) { log_printf(L_ERROR, "Listening on port %d failed: %s\n", local_port, strerror(errno)); close(bind_sockfd); exit(1); } log_printf(L_DEBUG, "Bound to local port %d\n", local_port); return 0; } /* Bind the client.sockfd to a tunnel */ int handle_acktunnel_frame(protocol_frame *rcvd_frame) { tunnel *tun; if(!client_mode) { log_printf(L_WARNING, "Got ACK tunnel frame when not in client mode!?\n"); return -1; } 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_pipe_mode) { update_select_nfds(tun->sockfd); FD_SET(tun->sockfd, &client_master_fdset); if(client_local_port_mode) { log_printf(L_INFO, "Accepted a new connection on port %d\n", local_port); } } else { log_printf(L_ERROR, "This tunnel mode is not supported yet\n"); exit(1); } return 0; } /* Handle a TCP frame received from server */ int handle_server_tcp_frame(protocol_frame *rcvd_frame) { int offset = 0; tunnel *tun = NULL; int tun_id = rcvd_frame->connid; HASH_FIND_INT(by_id, &tun_id, tun); if(!tun) { log_printf(L_WARNING, "Got TCP frame with unknown tunnel ID %d\n", rcvd_frame->connid); return -1; } while(offset < rcvd_frame->data_length) { int sent_bytes; if(client_pipe_mode) { sent_bytes = write( 1, /* STDOUT */ rcvd_frame->data + offset, rcvd_frame->data_length - offset ); } else { sent_bytes = send( tun->sockfd, rcvd_frame->data + offset, rcvd_frame->data_length - offset, MSG_NOSIGNAL ); } if(sent_bytes < 0) { uint8_t data[PROTOCOL_BUFFER_OFFSET]; protocol_frame frame_st, *frame; log_printf(L_INFO, "Could not write to socket: %s\n", 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); if(tun->sockfd) { FD_CLR(tun->sockfd, &client_master_fdset); } 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; } /* Delete tunnel and clear client-side fdset */ void client_close_tunnel(tunnel *tun) { if(tun->sockfd) { FD_CLR(tun->sockfd, &client_master_fdset); } tunnel_delete(tun); } /* Handle close-tunnel frame recived from the server */ int handle_server_tcp_fin_frame(protocol_frame *rcvd_frame) { tunnel *tun=NULL; int connid = rcvd_frame->connid; HASH_FIND_INT(by_id, &connid, tun); if(!tun) { log_printf(L_WARNING, "Got TCP FIN frame with unknown tunnel ID %d\n", rcvd_frame->connid); return -1; } if(tun->friendnumber != rcvd_frame->friendnumber) { log_printf(L_WARNING, "Friend #%d tried to close tunnel while server is #%d\n", rcvd_frame->friendnumber, tun->friendnumber); return -1; } client_close_tunnel(tun); return 0; } /* Close and delete all tunnels (when server went offline) */ void client_close_all_connections() { tunnel *tmp = NULL; tunnel *tun = NULL; HASH_ITER(hh, by_id, tun, tmp) { client_close_tunnel(tun); } } /* Main loop for the client */ int do_client_loop(uint8_t *tox_id_str) { unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; unsigned char tox_id[TOX_ADDRESS_SIZE]; uint32_t friendnumber = 0; TOX_CONNECTION last_friend_connection_status = TOX_CONNECTION_NONE; time_t last_friend_connection_status_received = 0; time_t connection_lost_timestamp = 0; struct timeval tv; fd_set fds; static time_t invitation_sent_time = 0; uint32_t invitations_sent = 0; TOX_ERR_FRIEND_QUERY friend_query_error; TOX_ERR_FRIEND_CUSTOM_PACKET custom_packet_error; client_tunnel.sockfd = 0; FD_ZERO(&client_master_fdset); tox_callback_friend_lossless_packet(tox, parse_lossless_packet); if(!string_to_id(tox_id, tox_id_str)) { log_printf(L_ERROR, "Invalid Tox ID"); exit(1); } if(!ping_mode && !client_pipe_mode) { local_bind(); signal(SIGPIPE, SIG_IGN); } log_printf(L_INFO, "Connecting to Tox...\n"); while(1) { /* Let tox do its stuff */ tox_iterate(tox, NULL); switch(state) { /* * Send friend request */ case CLIENT_STATE_INITIAL: if(connection_status != TOX_CONNECTION_NONE) { state = CLIENT_STATE_CONNECTED; } break; case CLIENT_STATE_CONNECTED: { uint8_t* data = (uint8_t *)"Hi, fellow tuntox instance!"; uint16_t length = sizeof(data); /* https://github.com/TokTok/c-toxcore/blob/acb6b2d8543c8f2ea0c2e60dc046767cf5cc0de8/toxcore/tox.h#L1168 */ TOX_ERR_FRIEND_ADD add_error; if(use_shared_secret) { data = (uint8_t *)shared_secret; data[TOX_MAX_FRIEND_REQUEST_LENGTH-1] = '\0'; length = strlen((char *)data)+1; log_printf(L_DEBUG, "Sent shared secret of length %u\n", length); } if(invitations_sent == 0) { log_printf(L_INFO, "Connected. Sending friend request.\n"); } else { log_printf(L_INFO, "Sending another friend request.\n"); } friendnumber = tox_friend_add( tox, tox_id, data, length, &add_error ); if(add_error != TOX_ERR_FRIEND_ADD_OK) { unsigned char tox_printable_id[TOX_ADDRESS_SIZE * 2 + 1]; id_to_string(tox_printable_id, tox_id); log_printf(L_ERROR, "Error %u adding friend %s\n", add_error, tox_printable_id); exit(-1); } invitation_sent_time = time(NULL); invitations_sent++; state = CLIENT_STATE_SENTREQUEST; log_printf(L_INFO, "Waiting for friend to accept us...\n"); } break; case CLIENT_STATE_SENTREQUEST: { TOX_CONNECTION friend_connection_status; friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) { log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u", friend_query_error); } else { last_friend_connection_status_received = time(NULL); last_friend_connection_status = friend_connection_status; if(friend_connection_status != TOX_CONNECTION_NONE) { const char* status = readable_connection_status(friend_connection_status); log_printf(L_INFO, "Friend request accepted (%s)!\n", status); state = CLIENT_STATE_REQUEST_ACCEPTED; } else { if(1 && (time(NULL) - invitation_sent_time > 45)) { TOX_ERR_FRIEND_DELETE error = 0; log_printf(L_INFO, "Sending another friend request..."); tox_friend_delete( tox, friendnumber, &error); if(error != TOX_ERR_FRIEND_DELETE_OK) { log_printf(L_ERROR, "Error %u deleting friend before reconnection\n", error); exit(-1); } state = CLIENT_STATE_CONNECTED; } } } break; } case CLIENT_STATE_REQUEST_ACCEPTED: if(ping_mode) { state = CLIENT_STATE_SEND_PING; } else if(client_pipe_mode) { state = CLIENT_STATE_SETUP_PIPE; } else { state = CLIENT_STATE_BIND_PORT; } break; case CLIENT_STATE_SEND_PING: /* Send the ping packet */ { uint8_t data[] = { 0xa2, 0x6a, 0x01, 0x08, 0x00, 0x00, 0x00, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f }; clock_gettime(CLOCK_MONOTONIC, &ping_sent_time); tox_friend_send_lossless_packet( tox, friendnumber, data, sizeof(data), &custom_packet_error ); } if(custom_packet_error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { state = CLIENT_STATE_PING_SENT; } else { log_printf(L_WARNING, "When sending ping packet: %u", custom_packet_error); } break; case CLIENT_STATE_PING_SENT: /* Just sit there and wait for pong */ break; case CLIENT_STATE_BIND_PORT: if(bind_sockfd < 0) { log_printf(L_ERROR, "Shutting down - could not bind to listening port\n"); state = CLIENT_STATE_SHUTDOWN; } else { state = CLIENT_STATE_FORWARDING; } break; case CLIENT_STATE_SETUP_PIPE: send_tunnel_request_packet( remote_host, remote_port, friendnumber ); state = CLIENT_STATE_FORWARDING; break; case CLIENT_STATE_REQUEST_TUNNEL: send_tunnel_request_packet( remote_host, remote_port, friendnumber ); state = CLIENT_STATE_WAIT_FOR_ACKTUNNEL; break; case CLIENT_STATE_WAIT_FOR_ACKTUNNEL: client_tunnel.sockfd = 0; send_tunnel_request_packet( remote_host, remote_port, friendnumber ); break; case CLIENT_STATE_FORWARDING: { int accept_fd = 0; int select_rv = 0; tunnel *tmp = NULL; tunnel *tun = NULL; tv.tv_sec = 0; tv.tv_usec = 20000; fds = client_master_fdset; /* Handle accepting new connections */ if(!client_pipe_mode && client_tunnel.sockfd <= 0) /* Don't accept if we're already waiting to establish a tunnel */ { accept_fd = accept(bind_sockfd, NULL, NULL); if(accept_fd != -1) { log_printf(L_INFO, "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 ); } } /* Handle reading from sockets */ select_rv = select(select_nfds, &fds, NULL, NULL, &tv); if(select_rv == -1 || select_rv == 0) { if(select_rv == -1) { log_printf(L_DEBUG, "Reading from local socket failed: code=%d (%s)\n", errno, strerror(errno)); } else { log_printf(L_DEBUG2, "Nothing to read..."); } } else { HASH_ITER(hh, by_id, tun, tmp) { if(FD_ISSET(tun->sockfd, &fds)) { int nbytes; if(client_local_port_mode) { nbytes = recv(tun->sockfd, tox_packet_buf + PROTOCOL_BUFFER_OFFSET, READ_BUFFER_SIZE, 0); } else { nbytes = read(tun->sockfd, tox_packet_buf + PROTOCOL_BUFFER_OFFSET, READ_BUFFER_SIZE ); } /* Check if connection closed */ if(nbytes == 0) { uint8_t data[PROTOCOL_BUFFER_OFFSET]; protocol_frame frame_st, *frame; log_printf(L_INFO, "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); if(tun->sockfd) { FD_CLR(tun->sockfd, &client_master_fdset); } 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); } } } } fds = client_master_fdset; /* Check friend connection status changes */ /* TODO: learned about tox_friend_connection_status_cb after writing this... */ /* TODO: also check friend status tox_callback_friend_status */ if(time(NULL) - last_friend_connection_status_received > 15) { TOX_CONNECTION friend_connection_status; friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) { log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u\n", friend_query_error); } else { if(friend_connection_status != last_friend_connection_status) { const char* status = readable_connection_status(friend_connection_status); log_printf(L_INFO, "Friend connection status changed to: %s (%d)\n", status, friend_connection_status); if(friend_connection_status == TOX_CONNECTION_NONE) { state = CLIENT_STATE_CONNECTION_LOST; connection_lost_timestamp = time(NULL); } } last_friend_connection_status_received = time(NULL); last_friend_connection_status = friend_connection_status; } } } break; case CLIENT_STATE_CONNECTION_LOST: { TOX_CONNECTION friend_connection_status; friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) { log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u\n", friend_query_error); } else { if(friend_connection_status == TOX_CONNECTION_NONE) { /* https://github.com/TokTok/c-toxcore/blob/acb6b2d8543c8f2ea0c2e60dc046767cf5cc0de8/toxcore/tox.h#L1267 */ TOX_ERR_FRIEND_DELETE tox_delete_error; log_printf(L_WARNING, "Lost connection to server, closing all tunnels and re-adding friend\n"); client_close_all_connections(); tox_friend_delete(tox, friendnumber, &tox_delete_error); if(tox_delete_error) { log_printf(L_ERROR, "Error when deleting server from friend list: %d\n", tox_delete_error); } state = CLIENT_STATE_INITIAL; } else { state = CLIENT_STATE_FORWARDING; } } } break; case 0xffffffff: log_printf(L_ERROR, "You forgot a break statement\n"); case CLIENT_STATE_SHUTDOWN: exit(0); break; } usleep(tox_iteration_interval(tox) * 1000); } }