/*
* Project: udptunnel
* File: client.c
*
* Copyright (C) 2009 Daniel Meekins
* Contact: dmeekins - gmail
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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, see .
*/
#include
#include
#ifndef WIN32
#include
#endif /*WIN32*/
#include "common.h"
#include "client.h"
#include "socket.h"
extern int debug_level;
/*
* Allocates and initializes a new client object.
* id - ID number for the client to have
* tcp_sock/udp_sock - sockets attributed to the client. this function copies
* the structure, so the calling function can free the sockets passed to
* here.
* connected - whether the TCP socket is connected or not.
* Returns a pointer to the new structure. Call client_free() when done with
* it.
*/
client_t *client_create(uint16_t id, socket_t *tcp_sock, socket_t *udp_sock,
int connected)
{
client_t *c = NULL;
c = calloc(1, sizeof(client_t));
if(!c)
goto error;
c->id = id;
c->tcp_sock = sock_copy(tcp_sock);
c->udp_sock = sock_copy(udp_sock);
c->udp2tcp_state = CLIENT_WAIT_HELLO;
c->tcp2udp_state = CLIENT_WAIT_DATA0;
c->connected = connected;
timerclear(&c->keepalive);
timerclear(&c->tcp2udp_timeout);
c->resend_count = 0;
return c;
error:
if(c)
{
if(c->tcp_sock)
sock_free(c->tcp_sock);
if(c->udp_sock)
sock_free(c->udp_sock);
free(c);
}
return NULL;
}
/*
* Performs a deep copy of the client structure.
*/
client_t *client_copy(client_t *dst, client_t *src, size_t len)
{
if(!dst || !src)
return NULL;
memcpy(dst, src, sizeof(*src));
dst->tcp_sock = sock_copy(src->tcp_sock);
if(!dst->tcp_sock)
return NULL;
dst->udp_sock = sock_copy(src->udp_sock);
if(!dst->udp_sock)
return NULL;
return dst;
}
/*
* Compares the ID of the two clients.
*/
int client_cmp(client_t *c1, client_t *c2, size_t len)
{
return c1->id - c2->id;
}
/*
* Connects the TCP socket of the client (wrapper for sock_connect()). Returns
* 0 on success or -1 on error.
*/
int client_connect_tcp(client_t *c, char *port)
{
if(!c->connected)
{
if(sock_connect(c->tcp_sock, 0, port) == 0)
{
c->connected = 1;
return 0;
}
}
return -1;
}
/*
* Closes the TCP socket for the client (wrapper for sock_close()).
*/
void client_disconnect_tcp(client_t *c)
{
if(c->connected)
{
sock_close(c->tcp_sock);
c->connected = 0;
}
}
/*
* Closes the UDP socket for the client (wrapper for sock_close()).
*/
void client_disconnect_udp(client_t *c)
{
sock_close(c->udp_sock);
}
/*
* Releases the memory used by the client.
*/
void client_free(client_t *c)
{
if(c)
{
sock_free(c->tcp_sock);
sock_free(c->udp_sock);
free(c);
}
}
/*
* Receives a message from the UDP tunnel for the client. Only used in
* udpclient program because each client has their own UDP socket. Returns 0
* for success or -1 on error. The data is written to memory pointed to by
* data, and the id, msg_type, and len are set from the message header.
*/
int client_recv_udp_msg(client_t *client, char *data, int data_len,
uint16_t *id, uint8_t *msg_type, uint16_t *len)
{
int ret;
socket_t from;
ret = msg_recv_msg(client->udp_sock, &from, data, data_len,
id, msg_type, len);
if(ret < 0)
return ret;
if(!sock_addr_equal(client->udp_sock, &from))
return -1;
return 0;
}
/*
* Copy data to the internal buffer for sending to tcp connection and send ACK
* back to tunnel. Returns 0 on success, 1 if this was "resending" data, -1
* on error, or -2 if need to disconnect.
*/
int client_got_udp_data(client_t *client, char *data, int data_len,
uint8_t msg_type)
{
int ret;
int is_resend = 0;
if(data_len > MSG_MAX_LEN)
return -1;
/* Check if got new data, which is when got the data type (DATA0 or DATA1)
that it was waiting for, and write that new data to the buffer. */
if((msg_type == MSG_TYPE_DATA0 &&
client->udp2tcp_state == CLIENT_WAIT_DATA0)
|| (msg_type == MSG_TYPE_DATA1 &&
client->udp2tcp_state == CLIENT_WAIT_DATA1))
{
memcpy(client->udp2tcp, data, data_len);
client->udp2tcp_len = data_len;
}
else
is_resend = 1; /* Otherwise, the other host resent the data */
msg_type = (msg_type == MSG_TYPE_DATA0) ? MSG_TYPE_ACK0 : MSG_TYPE_ACK1;
/* Send the ACK for the data */
ret = msg_send_msg(client->udp_sock, client->id, msg_type, NULL, 0);
if(ret < 0)
return ret;
if(is_resend)
return 1;
/* Set the state to wait for the next type of data */
client->udp2tcp_state = client->udp2tcp_state == CLIENT_WAIT_DATA0 ?
CLIENT_WAIT_DATA1 : CLIENT_WAIT_DATA0;
return 0;
}
/*
* Send data received from UDP tunnel to TCP connection. Need to call
* client_got_udp_data() first. Returns -1 on general error, -2 if need to
* disconnect, and 0 on success.
*/
int client_send_tcp_data(client_t *client)
{
int ret;
ret = sock_send(client->tcp_sock, client->udp2tcp, client->udp2tcp_len);
if(ret < 0)
return -1;
else if(ret == 0)
return -2;
else
return 0;
}
/*
* Reads data that is ready on the TCP socket and stores it in the internal
* buffer. The routine client_send_udp_data() send that data to the tunnel.
*/
int client_recv_tcp_data(client_t *client)
{
int ret;
/* Don't read the tcp data yet if waiting for an ack or the hello */
if(client->tcp2udp_state == CLIENT_WAIT_ACK0 ||
client->tcp2udp_state == CLIENT_WAIT_ACK1 ||
client->udp2tcp_state == CLIENT_WAIT_HELLO)
return 1;
ret = sock_recv(client->tcp_sock, NULL, client->tcp2udp,
sizeof(client->tcp2udp));
if(ret < 0)
return -1;
if(ret == 0)
return -2;
client->tcp2udp_len = ret;
return 0;
}
/*
* Sends the data in the tcp2udp buffer to the UDP tunnel. Returns 0 for
* success, -1 on error, and -2 if needs to disconnect.
*/
int client_send_udp_data(client_t *client)
{
uint8_t msg_type;
int ret;
if(client->resend_count >= CLIENT_MAX_RESEND)
return -2;
/* Set the message type it is sending. If the client is in the WAIT_ACK
state, then it will send the same type of data again (since this would
have been called b/c of a timeout. */
switch(client->tcp2udp_state)
{
case CLIENT_WAIT_DATA0:
case CLIENT_WAIT_ACK0:
msg_type = MSG_TYPE_DATA0;
break;
case CLIENT_WAIT_DATA1:
case CLIENT_WAIT_ACK1:
msg_type = MSG_TYPE_DATA1;
break;
default:
return -1;
}
ret = msg_send_msg(client->udp_sock, client->id, msg_type,
client->tcp2udp, client->tcp2udp_len);
if(ret < 0)
return ret;
/* Set the state to wait for an ACK and set the timeout to some time in
the future */
client->tcp2udp_state = (msg_type == MSG_TYPE_DATA0) ?
CLIENT_WAIT_ACK0 : CLIENT_WAIT_ACK1;
gettimeofday(&client->tcp2udp_timeout, NULL);
client->tcp2udp_timeout.tv_sec += (client->resend_count+1)*CLIENT_TIMEOUT;
return 0;
}
/*
* Notifies the client that it got an ACK to change the internal state to
* wait for data. Returns 0 if ok or -1 if something weird happened.
*/
int client_got_ack(client_t *client, uint8_t ack_type)
{
if(ack_type == MSG_TYPE_ACK0 && client->tcp2udp_state == CLIENT_WAIT_ACK0)
{
client->tcp2udp_state = CLIENT_WAIT_DATA1;
client->resend_count = 0;
return 0;
}
if(ack_type == MSG_TYPE_ACK1 && client->tcp2udp_state == CLIENT_WAIT_ACK1)
{
client->tcp2udp_state = CLIENT_WAIT_DATA0;
client->resend_count = 0;
return 0;
}
return -1;
}
/*
* Sends a HELLO type message to the udpserver (proxy) to tell it to make a
* TCP connection to the specified host:port.
*/
int client_send_hello(client_t *client, char *host, char *port,
uint16_t req_id)
{
return msg_send_hello(client->udp_sock, host, port, req_id);
}
/*
* Sends a Hello ACK to the UDP tunnel.
*/
int client_send_helloack(client_t *client, uint16_t req_id)
{
req_id = htons(req_id);
return msg_send_msg(client->udp_sock, client->id, MSG_TYPE_HELLOACK,
(char *)&req_id, sizeof(req_id));
}
/*
* Notify the client that it got a Hello ACK.
*/
int client_got_helloack(client_t *client)
{
if(client->udp2tcp_state == CLIENT_WAIT_HELLO)
client->udp2tcp_state = CLIENT_WAIT_DATA0;
return 0;
}
/*
* Sends a goodbye message to the UDP server.
*/
int client_send_goodbye(client_t *client)
{
return msg_send_msg(client->udp_sock, client->id, MSG_TYPE_GOODBYE,
NULL, 0);
}
/*
* Checks the timeout state of the client and resend the data if the timeout
* is up.
*/
int client_check_and_resend(client_t *client, struct timeval curr_tv)
{
if((client->tcp2udp_state == CLIENT_WAIT_ACK0 ||
client->tcp2udp_state == CLIENT_WAIT_ACK1)
&& timercmp(&curr_tv, &client->tcp2udp_timeout, >))
{
client->resend_count++;
if(debug_level >= DEBUG_LEVEL2)
printf("client(%d): resending data, count %d\n",
CLIENT_ID(client), client->resend_count);
return client_send_udp_data(client);
}
return 0;
}
/*
* Sends a keepalive message to the UDP server.
*/
int client_check_and_send_keepalive(client_t *client, struct timeval curr_tv)
{
if(client_timed_out(client, curr_tv))
{
curr_tv.tv_sec += KEEP_ALIVE_SECS;
memcpy(&client->keepalive, &curr_tv, sizeof(struct timeval));
return msg_send_msg(client->udp_sock, client->id, MSG_TYPE_KEEPALIVE,
NULL, 0);
}
return 0;
}
/*
* Sets the client's keepalive timeout to be the current time plus the timeout
* period.
*/
void client_reset_keepalive(client_t *client)
{
struct timeval curr;
gettimeofday(&curr, NULL);
curr.tv_sec += KEEP_ALIVE_TIMEOUT_SECS;
memcpy(&client->keepalive, &curr, sizeof(struct timeval));
}
/*
* Returns 1 if the client timed out (didn't get any data or keep alive
* messages in the period), or 0 if it hasn't yet.
*/
int client_timed_out(client_t *client, struct timeval curr_tv)
{
if(timercmp(&curr_tv, &client->keepalive, >))
return 1;
else
return 0;
}