When receiving unexpected UDP packets, check if they match a known IP and forward them.

This will not work for many clients that come from a single IP but will
work-around issues, when clients are behind a NAT that keeps their UDP port
state for shorter time than DPD.
This commit is contained in:
Nikos Mavrogiannopoulos
2014-04-19 10:29:57 +02:00
parent 8e73f98502
commit 489368c58e
3 changed files with 104 additions and 50 deletions

View File

@@ -138,7 +138,7 @@ message tun_mtu_msg
/* UDP_FD */
message udp_fd_msg
{
required bool hello = 1 [default = true]; /* is that a client hello? */
}
/* SESSION_INFO */

View File

@@ -621,7 +621,7 @@ static int forward_udp_to_owner(main_server_st* s, struct listener_st *listener)
{
int ret, e;
struct sockaddr_storage cli_addr;
struct proc_st *ctmp = NULL;
struct proc_st *ctmp = NULL, *proc_to_send = NULL;
socklen_t cli_addr_size;
uint8_t buffer[1024];
char tbuf[64];
@@ -629,6 +629,7 @@ uint8_t *session_id;
int session_id_size;
ssize_t buffer_size;
int connected = 0;
int match_ip_only = 0, matching_ips;
time_t now;
/* first receive from the correct client and connect socket */
@@ -650,6 +651,7 @@ time_t now;
human_addr((struct sockaddr*)&cli_addr, cli_addr_size, tbuf, sizeof(tbuf)),
(unsigned int)buffer[1], (unsigned int)buffer[2],
(unsigned int)buffer[RECORD_PAYLOAD_POS], (unsigned int)buffer[RECORD_PAYLOAD_POS+1]);
if (buffer[1] != 254 && (buffer[1] != 1 && buffer[2] != 0) &&
buffer[RECORD_PAYLOAD_POS] != 254 && (buffer[RECORD_PAYLOAD_POS] != 0 && buffer[RECORD_PAYLOAD_POS+1] != 0)) {
mslog(s, NULL, LOG_INFO, "unknown DTLS version: %u.%u", (unsigned)buffer[1], (unsigned)buffer[2]);
@@ -657,51 +659,77 @@ time_t now;
}
if (buffer[0] != 22) {
mslog(s, NULL, LOG_INFO, "unexpected DTLS content type: %u", (unsigned int)buffer[0]);
goto fail;
/* Here we received a non-client hello packet. It may be that
* the client's NAT changed it's UDP source port and the previous
* connection is invalidated. Try to see if we can simply match
* the IP address and forward the socket.
*/
match_ip_only = 1;
} else {
/* read session_id */
session_id_size = buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS];
session_id = &buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS+1];
}
/* read session_id */
session_id_size = buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS];
session_id = &buffer[RECORD_PAYLOAD_POS+HANDSHAKE_SESSION_ID_POS+1];
/* search for the IP and the session ID in all procs */
now = time(0);
matching_ips = 0;
list_for_each(&s->proc_list.head, ctmp, list) {
if (session_id_size == ctmp->dtls_session_id_size &&
if (match_ip_only == 0 && session_id_size == ctmp->dtls_session_id_size &&
memcmp(session_id, ctmp->dtls_session_id, session_id_size) == 0) {
UdpFdMsg msg = UDP_FD_MSG__INIT;
if (now - ctmp->udp_fd_receive_time <= UDP_FD_RESEND_TIME) {
mslog(s, ctmp, LOG_INFO, "received UDP connection too soon");
break;
}
ret = connect(listener->fd, (void*)&cli_addr, cli_addr_size);
if (ret == -1) {
e = errno;
mslog(s, ctmp, LOG_ERR, "connect UDP socket: %s", strerror(e));
break;
}
ret = send_socket_msg_to_worker(s, ctmp, CMD_UDP_FD,
listener->fd,
&msg,
(pack_size_func)udp_fd_msg__get_packed_size,
(pack_func)udp_fd_msg__pack);
if (ret < 0) {
mslog(s, ctmp, LOG_ERR, "error passing UDP socket");
break;
}
mslog(s, ctmp, LOG_DEBUG, "passed UDP socket");
ctmp->udp_fd_receive_time = now;
connected = 1;
reopen_udp_port(listener);
proc_to_send = ctmp;
break;
} else if (match_ip_only != 0 && cli_addr_size == ctmp->remote_addr_len &&
memcmp(SA_IN_P_GENERIC(&cli_addr, cli_addr_size),
SA_IN_P_GENERIC(&ctmp->remote_addr, ctmp->remote_addr_len),
SA_IN_SIZE(ctmp->remote_addr_len)) == 0) {
matching_ips++;
proc_to_send = ctmp;
}
}
if (proc_to_send != 0) {
UdpFdMsg msg = UDP_FD_MSG__INIT;
if (matching_ips > 1) {
mslog(s, proc_to_send, LOG_INFO, "cannot associate with a client; more than a single clients from this IP");
goto fail;
}
if (now - proc_to_send->udp_fd_receive_time <= UDP_FD_RESEND_TIME) {
mslog(s, proc_to_send, LOG_INFO, "received UDP connection too soon");
goto fail;
}
ret = connect(listener->fd, (void*)&cli_addr, cli_addr_size);
if (ret == -1) {
e = errno;
mslog(s, proc_to_send, LOG_ERR, "connect UDP socket: %s", strerror(e));
goto fail;
}
if (match_ip_only != 0) {
msg.hello = 0;
}
ret = send_socket_msg_to_worker(s, proc_to_send, CMD_UDP_FD,
listener->fd,
&msg,
(pack_size_func)udp_fd_msg__get_packed_size,
(pack_func)udp_fd_msg__pack);
if (ret < 0) {
mslog(s, proc_to_send, LOG_ERR, "error passing UDP socket");
goto fail;
}
mslog(s, proc_to_send, LOG_DEBUG, "passed UDP socket");
proc_to_send->udp_fd_receive_time = now;
connected = 1;
reopen_udp_port(listener);
}
fail:
if (connected == 0) {
/* received packet from unknown host. Ignore it. */
@@ -709,7 +737,7 @@ fail:
return -1;
}
return 0;
}
@@ -771,14 +799,14 @@ unsigned total = 10;
static int check_tcp_wrapper(int fd)
{
struct request_info req;
if (request_init(&req, RQ_FILE, fd, RQ_DAEMON, PACKAGE_NAME, 0) == NULL)
return -1;
sock_host(&req);
if (hosts_access(&req) == 0)
return -1;
return 0;
}
#else
@@ -831,7 +859,7 @@ int main(int argc, char** argv)
/* Initialize GnuTLS */
tls_global_init(&s);
ret = gnutls_rnd(GNUTLS_RND_RANDOM, s.cookie_key, sizeof(s.cookie_key));
if (ret < 0) {
fprintf(stderr, "Error in cookie key generation\n");

View File

@@ -50,10 +50,7 @@ int handle_worker_commands(struct worker_st *ws)
uint16_t length;
int e;
struct msghdr hdr;
union {
char x[32];
struct sockaddr_storage ss;
} cmd_data;
uint8_t cmd_data[32];
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
@@ -70,7 +67,7 @@ int handle_worker_commands(struct worker_st *ws)
iov[1].iov_base = &length;
iov[1].iov_len = 2;
iov[2].iov_base = &cmd_data;
iov[2].iov_base = cmd_data;
iov[2].iov_len = sizeof(cmd_data);
memset(&hdr, 0, sizeof(hdr));
@@ -92,30 +89,57 @@ int handle_worker_commands(struct worker_st *ws)
exit(0);
}
oclog(ws, LOG_DEBUG, "worker received message %s of %u bytes\n", cmd_request_to_str(cmd), (unsigned)length);
if (length > ret - 3) {
oclog(ws, LOG_DEBUG, "worker received invalid message %s of %u bytes that claims to be %u\n", cmd_request_to_str(cmd), (unsigned)ret-3, (unsigned)length);
exit(1);
} else {
oclog(ws, LOG_DEBUG, "worker received message %s of %u bytes\n", cmd_request_to_str(cmd), (unsigned)length);
}
/*cmd_data_len = ret - 1;*/
switch(cmd) {
case CMD_TERMINATE:
exit(0);
case CMD_UDP_FD:
case CMD_UDP_FD: {
UdpFdMsg *tmsg;
unsigned hello = 1;
int fd;
if (ws->udp_state != UP_WAIT_FD) {
oclog(ws, LOG_INFO, "received another a UDP fd!");
}
tmsg = udp_fd_msg__unpack(NULL, length, cmd_data);
if (tmsg) {
hello = tmsg->hello;
udp_fd_msg__free_unpacked(tmsg, NULL);
}
if ( (cmptr = CMSG_FIRSTHDR(&hdr)) != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
if (cmptr->cmsg_level != SOL_SOCKET || cmptr->cmsg_type != SCM_RIGHTS) {
oclog(ws, LOG_ERR, "received UDP fd message of wrong type");
goto udp_fd_fail;
}
memcpy(&fd, CMSG_DATA(cmptr), sizeof(int));
if (hello == 0) {
/* only replace our session if we are inactive for more than 60 secs */
if ((ws->udp_state != UP_ACTIVE && ws->udp_state != UP_INACTIVE) ||
time(0) - ws->last_msg_udp < 60) {
oclog(ws, LOG_INFO, "received UDP fd message but our session is active!");
close(fd);
return 0;
}
} else { /* received client hello */
ws->udp_state = UP_SETUP;
}
if (ws->udp_fd != -1) {
close(ws->udp_fd);
}
memcpy(&ws->udp_fd, CMSG_DATA(cmptr), sizeof(int));
ws->udp_state = UP_SETUP;
ws->udp_fd = fd;
oclog(ws, LOG_DEBUG, "received new UDP fd and connected to peer");
return 0;
@@ -123,6 +147,8 @@ int handle_worker_commands(struct worker_st *ws)
oclog(ws, LOG_ERR, "could not receive peer's UDP fd");
return -1;
}
}
break;
default:
oclog(ws, LOG_ERR, "unknown CMD 0x%x", (unsigned)cmd);