freeswitch/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.c

885 lines
22 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2005 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@CFILE test_nat.c
* @brief Simulated NAT for testing
*
* NAT thing works so that we set the outgoing proxy URI to point
* towards its "private" address and give the real address of the proxy
* as its "public" address. If we use different IP families here, we may
* even manage to test real connectivity problems as proxy and endpoint
* can not talk to each other.
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Wed Mar 8 19:54:28 EET 2006
*/
#include "config.h"
struct nat;
struct binding;
#define SU_ROOT_MAGIC_T struct nat
#define SU_WAKEUP_ARG_T struct binding
#include <sofia-sip/su.h>
#include <sofia-sip/su_errno.h>
#include <sofia-sip/su_wait.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_localinfo.h>
#include <sofia-sip/su_log.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#define LIST_PROTOS(STORAGE, PREFIX, T) \
STORAGE void PREFIX ##_insert(T **list, T *node), \
PREFIX ##_remove(T *node)
#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV) \
STORAGE void PREFIX ##_insert(T **list, T *node) \
{ \
if ((node->NEXT = *list)) { \
node->PREV = node->NEXT->PREV; \
node->NEXT->PREV = &node->NEXT; \
} \
else \
node->PREV = list; \
*list = node; \
} \
STORAGE void PREFIX ##_remove(T *node) \
{ \
if (node->PREV) \
if ((*node->PREV = node->NEXT)) \
node->NEXT->PREV = node->PREV; \
node->PREV = NULL; \
} \
extern int LIST_DUMMY_VARIABLE
#include <test_nat.h>
struct nat {
su_home_t home[1];
su_root_t *parent;
su_clone_r clone;
tagi_t *tags;
su_root_t *root;
struct binding *bindings;
/* True if we act in symmetric way */
int symmetric;
/* True if we do logging */
int logging;
/* Everything sent to in_address will be forwarded to out_address */
su_sockaddr_t in_address[1], out_address[1];
socklen_t in_addrlen, out_addrlen;
int family; /* Preferred private family */
/* ...but source address will be "fake" */
su_localinfo_t *localinfo, *private, *fake;
su_socket_t udp_socket, tcp_socket;
int udp_register, tcp_register;
char buffer[65536];
};
LIST_PROTOS(static, nat_binding, struct binding);
struct binding
{
struct binding *next, **prev;
struct nat *nat; /* backpointer */
int socktype, protocol;
su_socket_t in_socket, out_socket;
int in_register, out_register;
int in_closed, out_closed;
char in_name[64], out_name[64];
};
static struct binding *nat_binding_new(struct nat *nat,
char const *protoname,
int socktype, int protocol,
int connected,
su_socket_t in_socket,
su_sockaddr_t *from,
socklen_t fromlen);
static void nat_binding_destroy(struct binding *);
static int binding_init(struct binding *b,
char const *protoname,
int connected,
su_localinfo_t *li,
su_sockaddr_t *from,
socklen_t fromlen);
static void flush_bindings(struct nat *nat);
static int invalidate_bindings(void *nat);
static int new_udp(struct nat *, su_wait_t *wait, struct binding *dummy);
static int udp_in_to_out(struct nat *, su_wait_t *wait, struct binding *);
static int udp_out_to_in(struct nat *, su_wait_t *wait, struct binding *);
static int new_tcp(struct nat *, su_wait_t *wait, struct binding *dummy);
static int tcp_in_to_out(struct nat *, su_wait_t *wait, struct binding *);
static int tcp_out_to_in(struct nat *, su_wait_t *wait, struct binding *);
static int invalidate_binding(struct binding *b);
/* nat entry point */
static int
test_nat_init(su_root_t *root, struct nat *nat)
{
su_localinfo_t *li, hints[1] = {{ 0 }};
int error;
unsigned port = 0, port0 = 0;
su_sockaddr_t su[1];
socklen_t sulen;
su_wait_t wait[1];
nat->root = root;
nat->udp_socket = INVALID_SOCKET, nat->tcp_socket = INVALID_SOCKET;
tl_gets(nat->tags,
TESTNATTAG_SYMMETRIC_REF(nat->symmetric),
TESTNATTAG_LOGGING_REF(nat->logging),
TAG_END());
hints->li_scope = LI_SCOPE_HOST | LI_SCOPE_SITE | LI_SCOPE_GLOBAL;
error = su_getlocalinfo(hints, &nat->localinfo);
if (error) {
fprintf(stderr, "test_nat: su_getlocalinfo: %s\n", su_gli_strerror(error));
return -1;
}
/* We must have two different IP addresses. */
if (!nat->localinfo || !nat->localinfo->li_next) {
fprintf(stderr, "test_nat: only one IP address available\n");
return -1;
}
for (li = nat->localinfo; li; li = li->li_next) {
if (nat->family == 0 || nat->family == li->li_family)
break;
}
if (li == NULL)
li = nat->localinfo;
memcpy(su, li->li_addr, sulen = li->li_addrlen);
memset(SU_ADDR(su), 0, SU_ADDRLEN(su));
nat->private = li;
/* Bind TCP and UDP to same port */
for (;;) {
nat->udp_socket = su_socket(li->li_family, SOCK_DGRAM, IPPROTO_UDP);
if (nat->udp_socket == INVALID_SOCKET)
return -1;
if (bind(nat->udp_socket, (void *)su, sulen) < 0) {
if (port0 == 0) {
su_perror("nat: bind(udp_socket)");
return -1;
}
fprintf(stderr, "test_nat: port %u: %s\n",
port, su_strerror(su_errno()));
su_close(nat->udp_socket);
nat->udp_socket = INVALID_SOCKET;
if (++port > 65535)
port = 1024;
if (port == port0) {
fprintf(stderr, "test_nat: could not find free port pairt\n");
return -1;
}
continue;
}
if (getsockname(nat->udp_socket, (void *)su, &sulen) < 0) {
su_perror("nat: getsockname(udp_socket)");
return -1;
}
if (port0 == 0) {
port = port0 = ntohs(su->su_port);
if (port0 == 0) {
fprintf(stderr, "test_nat: bind did not return port\n");
return -1;
}
}
nat->tcp_socket = su_socket(li->li_family, SOCK_STREAM, IPPROTO_TCP);
if (nat->tcp_socket == INVALID_SOCKET)
return -1;
if (bind(nat->tcp_socket, (void *)su, sulen) < 0) {
su_close(nat->tcp_socket);
nat->tcp_socket = INVALID_SOCKET;
fprintf(stderr, "test_nat: port %u: %s\n",
port, su_strerror(su_errno()));
if (++port > 65535)
port = 1024;
if (port == port0) {
fprintf(stderr, "test_nat: could not find free port pair\n");
return -1;
}
continue;
}
break;
}
memcpy(nat->in_address, li->li_addr, nat->in_addrlen = li->li_addrlen);
nat->in_address->su_port = su->su_port;
if (su_setreuseaddr(nat->udp_socket, 1) < 0) {
su_perror("nat: su_setreuseaddr(udp_socket)");
return -1;
}
if (listen(nat->tcp_socket, 5) < 0) {
su_perror("nat: listen(tcp_socket)");
return -1;
}
if (su_wait_create(wait, nat->udp_socket, SU_WAIT_IN) < 0) {
su_perror("nat: su_wait_create");
return -1;
}
nat->udp_register = su_root_register(root, wait, new_udp, NULL, 0);
if (nat->udp_register < 0) {
su_perror("nat: su_root_register");
return -1;
}
if (su_wait_create(wait, nat->tcp_socket, SU_WAIT_IN) < 0) {
su_perror("nat: su_wait_create");
return -1;
}
nat->tcp_register = su_root_register(root, wait, new_tcp, NULL, 0);
if (nat->tcp_register < 0) {
su_perror("nat: su_root_register");
return -1;
}
return 0;
}
static void
test_nat_deinit(su_root_t *root, struct nat *nat)
{
flush_bindings(nat);
if (nat->tcp_register)
su_root_deregister(root, nat->tcp_register);
if (nat->udp_register)
su_root_deregister(root, nat->udp_register);
if (nat->udp_socket != INVALID_SOCKET)
su_close(nat->udp_socket);
if (nat->tcp_socket != INVALID_SOCKET)
su_close(nat->tcp_socket);
su_freelocalinfo(nat->localinfo);
free(nat->tags);
}
struct nat *test_nat_create(su_root_t *root,
int family,
tag_type_t tag, tag_value_t value, ...)
{
struct nat *nat = su_home_new(sizeof *nat);
if (nat) {
ta_list ta;
nat->parent = root;
nat->family = family;
ta_start(ta, tag, value);
nat->tags = tl_llist(ta_tags(ta));
ta_end(ta);
if (su_clone_start(root,
nat->clone,
nat,
test_nat_init,
test_nat_deinit) == -1)
su_home_unref(nat->home), nat = NULL;
}
return nat;
}
void test_nat_destroy(struct nat *nat)
{
if (nat) {
su_clone_wait(nat->parent, nat->clone);
su_home_unref(nat->home);
}
}
/** Get "private" address. */
int test_nat_private(struct nat *nat, void *address, socklen_t *return_addrlen)
{
if (nat == NULL || address == NULL || return_addrlen == NULL)
return su_seterrno(EFAULT);
if (*return_addrlen < nat->in_addrlen)
return su_seterrno(EINVAL);
memcpy(address, nat->in_address, *return_addrlen = nat->in_addrlen);
return 0;
}
/** Set "public" address. */
int test_nat_public(struct nat *nat, void const *address, int addrlen)
{
su_sockaddr_t const *su = address;
su_localinfo_t *li;
if (nat == NULL)
return su_seterrno(EFAULT);
if (address == NULL) {
nat->fake = NULL;
return 0;
}
if ((size_t)addrlen > sizeof nat->out_address)
return su_seterrno(EINVAL);
for (li = nat->localinfo; li; li = li->li_next) {
if (li != nat->private &&
li->li_scope == LI_SCOPE_HOST &&
li->li_family == su->su_family)
break;
}
if (li == NULL)
for (li = nat->localinfo; li; li = li->li_next) {
if (li != nat->private && li->li_family == su->su_family)
break;
}
if (li == NULL)
return su_seterrno(EADDRNOTAVAIL);
su_clone_pause(nat->clone);
memcpy(nat->out_address, address, nat->out_addrlen = addrlen);
nat->fake = li;
su_clone_resume(nat->clone);
return 0;
}
int test_nat_flush(struct nat *nat)
{
if (nat == NULL)
return su_seterrno(EFAULT);
return su_task_execute(su_clone_task(nat->clone),
invalidate_bindings, nat, NULL);
}
/* ====================================================================== */
struct binding *nat_binding_new(struct nat *nat,
char const *protoname,
int socktype,
int protocol,
int connected,
su_socket_t in_socket,
su_sockaddr_t *from,
socklen_t fromlen)
{
struct binding *b;
if (nat->fake == NULL) { /* Xyzzy */
fprintf(stderr, "test_nat: fake address missing\n");
su_close(in_socket);
return NULL;
}
b = su_zalloc(nat->home, sizeof *b);
if (b == NULL) {
su_perror("nat_binding_new: su_zalloc");
su_close(in_socket);
return 0;
}
b->nat = nat;
b->socktype = socktype;
b->protocol = protocol;
b->in_socket = in_socket, b->out_socket = INVALID_SOCKET;
b->in_register = -1, b->out_register = -1;
if (binding_init(b, protoname, connected, nat->fake, from, fromlen) < 0)
nat_binding_destroy(b), b = NULL;
return b;
}
static int binding_init(struct binding *b,
char const *protoname,
int connected,
su_localinfo_t *li,
su_sockaddr_t *from,
socklen_t fromlen)
{
struct nat *nat = b->nat;
su_socket_t out_socket;
su_sockaddr_t addr[1];
socklen_t addrlen = (sizeof addr);
char ipname[64];
su_wait_t wait[1];
su_wakeup_f in_to_out, out_to_in;
if (b->socktype == SOCK_STREAM)
in_to_out = tcp_in_to_out, out_to_in = tcp_out_to_in;
else
in_to_out = udp_in_to_out, out_to_in = udp_out_to_in;
if (b->in_socket == INVALID_SOCKET) {
int in_socket;
in_socket = su_socket(from->su_family, b->socktype, b->protocol);
if (in_socket == INVALID_SOCKET) {
su_perror("nat_binding_new: socket");
return -1;
}
b->in_socket = in_socket;
if (su_setreuseaddr(in_socket, 1) < 0) {
su_perror("nat_binding_new: su_setreuseaddr(in_socket)");
return -1;
}
if (bind(in_socket, (void *)nat->in_address, nat->in_addrlen) < 0) {
su_perror("nat_binding_new: bind(in_socket)");
return -1;
}
if (connect(in_socket, (void *)from, fromlen) < 0) {
su_perror("nat_binding_new: connect(in_socket)");
return -1;
}
}
out_socket = su_socket(li->li_family, b->socktype, b->protocol);
if (out_socket == INVALID_SOCKET) {
su_perror("nat_binding_new: socket");
return -1;
}
b->out_socket = out_socket;
if (bind(out_socket, (void *)li->li_addr, li->li_addrlen) < 0) {
su_perror("nat_binding_new: bind(to)");
return -1;
}
if (connected)
if (connect(out_socket, (void *)nat->out_address, nat->out_addrlen) < 0) {
su_perror("nat_binding_new: connect(to)");
return -1;
}
getpeername(b->in_socket, (void *)addr, &addrlen);
inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname);
snprintf(b->in_name, sizeof b->in_name,
addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
ipname, ntohs(addr->su_port));
getsockname(out_socket, (void *)addr, &addrlen);
inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname);
snprintf(b->out_name, sizeof b->out_name,
addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
ipname, ntohs(addr->su_port));
if (su_wait_create(wait, b->in_socket, SU_WAIT_IN) < 0) {
su_perror("nat_binding_new: su_wait_create");
return -1;
}
b->in_register = su_root_register(nat->root, wait, in_to_out, b, 0);
if (b->in_register < 0) {
su_perror("nat_binding_new: su_root_register");
su_wait_destroy(wait);
return -1;
}
if (su_wait_create(wait, out_socket, SU_WAIT_IN) < 0) {
su_perror("nat_binding_new: su_wait_create");
return -1;
}
b->out_register = su_root_register(nat->root, wait, out_to_in, b, 0);
if (b->out_register < 0) {
su_perror("nat_binding_new: su_root_register");
su_wait_destroy(wait);
return -1;
}
nat_binding_insert(&nat->bindings, b);
if (nat->logging)
printf("nat: new %s binding %s <=> %s\n",
protoname, b->in_name, b->out_name);
return 0;
}
static void nat_binding_destroy(struct binding *b)
{
nat_binding_remove(b);
if (b->in_register != -1)
su_root_deregister(b->nat->root, b->in_register);
if (b->out_register != -1)
su_root_deregister(b->nat->root, b->out_register);
su_close(b->in_socket), su_close(b->out_socket);
}
static void flush_bindings(struct nat *nat)
{
struct binding *b;
for (b = nat->bindings; b; b = b->next) {
if (b->in_register)
su_root_deregister(nat->root, b->in_register);
su_close(b->in_socket);
if (b->out_register)
su_root_deregister(nat->root, b->out_register);
su_close(b->out_socket);
}
}
static int invalidate_bindings(void *arg)
{
struct nat *nat = arg;
struct binding *b;
for (b = nat->bindings; b; b = b->next) {
invalidate_binding(b);
}
return 0;
}
#if 0
static struct binding *nat_binding_find(struct nat *nat,
su_sockaddr_t *from,
int fromlen)
{
char name[64], ipname[64];
size_t namelen;
struct binding *b;
inet_ntop(from->su_family, SU_ADDR(from), ipname, sizeof ipname);
snprintf(name, sizeof name,
from->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
ipname, ntohs(from->su_port));
namelen = strlen(name) + 1;
for (b = nat->bindings; b; b = b->next) {
if (memcmp(name, b->in_name, namelen) == 0)
return b;
}
if (b == NULL)
b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric,
INVALID_SOCKET, from, fromlen);
return b;
}
#endif
/* ====================================================================== */
LIST_BODIES(static, nat_binding, struct binding, next, prev);
/* ====================================================================== */
static int new_udp(struct nat *nat, su_wait_t *wait, struct binding *dummy)
{
int events;
su_sockaddr_t from[1];
socklen_t fromlen = (sizeof from);
struct binding *b;
ssize_t n, m;
events = su_wait_events(wait, nat->udp_socket);
n = su_recvfrom(nat->udp_socket, nat->buffer, sizeof nat->buffer, 0,
from, &fromlen);
if (n < 0) {
su_perror("new_udp: recvfrom");
return 0;
}
b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric,
INVALID_SOCKET, from, fromlen);
if (b == NULL)
return 0;
if (nat->symmetric)
m = su_send(b->out_socket, nat->buffer, n, 0);
else
m = su_sendto(b->out_socket, nat->buffer, n, 0,
nat->out_address, nat->out_addrlen);
if (nat->logging)
printf("nat: udp out %d/%d %s => %s\n",
(int)m, (int)n, b->in_name, b->out_name);
return 0;
}
static int udp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b)
{
int events;
ssize_t n, m;
events = su_wait_events(wait, b->in_socket);
n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0);
if (n < 0) {
su_perror("udp_in_to_out: recv");
return 0;
}
if (nat->symmetric)
m = su_send(b->out_socket, nat->buffer, n, 0);
else
m = su_sendto(b->out_socket, nat->buffer, n, 0,
nat->out_address, nat->out_addrlen);
if (nat->logging)
printf("nat: udp out %d/%d %s => %s\n",
(int)m, (int)n, b->in_name, b->out_name);
return 0;
}
static int udp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b)
{
int events;
ssize_t n, m;
events = su_wait_events(wait, b->out_socket);
n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0);
if (n < 0) {
su_perror("udp_out_to_out: recv");
return 0;
}
m = su_send(b->in_socket, nat->buffer, n, 0);
if (nat->logging)
printf("nat: udp in %d/%d %s => %s\n",
(int)m, (int)n, b->out_name, b->in_name);
return 0;
}
/* ====================================================================== */
static int new_tcp(struct nat *nat, su_wait_t *wait, struct binding *dummy)
{
int events;
su_socket_t in_socket;
su_sockaddr_t from[1];
socklen_t fromlen = (sizeof from);
struct binding *b;
events = su_wait_events(wait, nat->tcp_socket);
in_socket = accept(nat->tcp_socket, (void *)from, &fromlen);
if (in_socket == INVALID_SOCKET) {
su_perror("new_tcp: accept");
return 0;
}
b = nat_binding_new(nat, "TCP", SOCK_STREAM, IPPROTO_TCP, 1,
in_socket, from, fromlen);
if (b == NULL)
return 0;
return 0;
}
static int tcp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b)
{
int events;
ssize_t n, m, o;
events = su_wait_events(wait, b->in_socket);
n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0);
if (n < 0) {
su_perror("tcp_in_to_out: recv");
return 0;
}
if (n == 0) {
if (nat->logging)
printf("nat: tcp out FIN %s => %s\n", b->in_name, b->out_name);
shutdown(b->out_socket, 1);
su_root_eventmask(nat->root, b->in_register, b->in_socket, 0);
b->in_closed = 1;
if (b->out_closed && b->in_closed)
nat_binding_destroy(b);
return 0;
}
for (m = 0; m < n; m += o) {
o = su_send(b->out_socket, nat->buffer + m, n - m, 0);
if (o < 0) {
su_perror("tcp_in_to_out: send");
break;
}
}
if (nat->logging)
printf("nat: tcp out %d/%d %s => %s\n",
(int)m, (int)n, b->in_name, b->out_name);
return 0;
}
static int tcp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b)
{
int events;
ssize_t n, m, o;
events = su_wait_events(wait, b->out_socket);
n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0);
if (n < 0) {
su_perror("tcp_out_to_in: recv");
return 0;
}
if (n == 0) {
if (nat->logging)
printf("nat: tcp out FIN %s => %s\n", b->out_name, b->in_name);
shutdown(b->in_socket, 1);
su_root_eventmask(nat->root, b->in_register, b->out_socket, 0);
b->out_closed = 1;
if (b->out_closed && b->in_closed)
nat_binding_destroy(b);
return 0;
}
for (m = 0; m < n; m += o) {
o = su_send(b->in_socket, nat->buffer + m, n - m, 0);
if (o < 0) {
if (su_errno() != EPIPE)
su_perror("tcp_in_to_out: send");
break;
}
}
if (nat->logging)
printf("nat: tcp in %d/%d %s => %s\n",
(int)m, (int)n, b->out_name, b->in_name);
return 0;
}
static int invalidate_binding(struct binding *b)
{
struct nat *nat = b->nat;
su_sockaddr_t addr[1];
socklen_t addrlen = (sizeof addr);
su_socket_t out;
int out_register;
su_wait_t wout[1];
char name[64];
out = su_socket(nat->fake->li_family, b->socktype, 0);
if (out == INVALID_SOCKET) {
su_perror("new_udp: socket");
return -1;
}
if (bind(out, (void *)nat->fake->li_addr, nat->fake->li_addrlen) < 0) {
su_perror("new_udp: bind(to)");
su_close(out);
return -1;
}
if (nat->symmetric)
if (connect(out, (void *)nat->out_address, nat->out_addrlen) < 0) {
su_perror("new_udp: connect(to)");
su_close(out);
return -1;
}
if (su_wait_create(wout, out, SU_WAIT_IN) < 0) {
su_perror("new_udp: su_wait_create");
su_close(out);
return -1;
}
if (b->socktype == SOCK_DGRAM)
out_register = su_root_register(nat->root, wout, udp_out_to_in, b, 0);
else
out_register = su_root_register(nat->root, wout, tcp_out_to_in, b, 0);
if (out_register < 0) {
su_perror("new_udp: su_root_register");
su_wait_destroy(wout);
su_close(out);
return -1;
}
su_root_deregister(nat->root, b->out_register);
su_close(b->out_socket);
b->out_socket = out;
b->out_register = out_register;
getsockname(out, (void *)addr, &addrlen);
inet_ntop(addr->su_family, SU_ADDR(addr), name, sizeof name);
snprintf(b->out_name, sizeof b->out_name,
addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
name, ntohs(addr->su_port));
if (nat->logging)
printf("nat: flushed binding %s <=> %s\n", b->in_name, b->out_name);
return 0;
}