Commit 0d128f9c authored by Robert Schmidt's avatar Robert Schmidt

Rewrite sctp_handle_new_association_req() to handle hostnames and IPv6

Use getaddrinfo() to look up IP (v4/v6) addresses. The old
interface of separately specifying IPv4 and IPv6 addresses is
preserved, but both fields allow to specify types of addresses, and we
will remove one field in the future.
parent e65cb0cf
...@@ -354,65 +354,67 @@ sctp_handle_new_association_req_multi( ...@@ -354,65 +354,67 @@ sctp_handle_new_association_req_multi(
ns, sctp_nb_cnx, assoc_id); ns, sctp_nb_cnx, assoc_id);
} }
//------------------------------------------------------------------------------ static const char *print_ip(struct addrinfo *p, char *buf, size_t buf_len)
static void
sctp_handle_new_association_req(
const instance_t instance,
const task_id_t requestor,
const sctp_new_association_req_t * const sctp_new_association_req_p)
{ {
int sd = 0; void *addr;
sctp_assoc_t assoc_id = 0; if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &ipv4->sin_addr;
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &ipv6->sin6_addr;
}
struct sctp_event_subscribe events={0}; // convert the IP to a string and print it:
return inet_ntop(p->ai_family, addr, buf, buf_len);
}
struct sctp_cnx_list_elm_s *sctp_cnx = NULL; static void sctp_handle_new_association_req(const instance_t instance,
const task_id_t requestor,
const sctp_new_association_req_t *const req)
{
enum sctp_connection_type_e connection_type = SCTP_TYPE_CLIENT; enum sctp_connection_type_e connection_type = SCTP_TYPE_CLIENT;
struct ifreq ifr; /* local address: IPv4 has priority, but can also handle IPv6 */
struct ifaddrs *ifaddr = NULL; const char *local = NULL;
struct ifaddrs *ifa = NULL; if (req->local_address.ipv6) {
int family = 0; local = req->local_address.ipv6_address;
int s = 0; SCTP_WARN("please specify IPv6 addresses in the IPv4 field, IPv4 handles both\n");
struct in_addr in; }
struct in6_addr in6; if (req->local_address.ipv4)
local = req->local_address.ipv4_address;
/* Prepare a new SCTP association as requested by upper layer and try to connect /* Prepare a new SCTP association as requested by upper layer and try to connect
* to remote host. * to remote host.
*/ */
DevAssert(sctp_new_association_req_p != NULL); DevAssert(req != NULL);
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_SCTP};
/* Create new socket with IPv6 affinity */ if (local == NULL)
//#warning "SCTP may Force IPv4 only, here" hints.ai_flags = AI_PASSIVE;
#ifdef NO_VIRTUAL_MACHINE struct addrinfo *serv;
// in init chunk appears a list of host addresses, IPv4 and IPv4 in an arbitrary (unsorted) order int status = getaddrinfo(local, NULL, &hints, &serv);
// SCTP hearbeats starts with first ipv4 addresses then stop triyng with other ipv4 addresses AssertFatal(status == 0, "getaddrinfo() failed: %s\n", gai_strerror(status));
// if it encounters an IPv6 address in list, so we can force the building of IPv4 addresses only
// with AF_INET (the working IPv4 address can be the last in the list...)
if ((sd = socket(AF_INET6, SOCK_STREAM, IPPROTO_SCTP)) < 0) {
#else
if ((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) < 0) { int sd;
#endif struct addrinfo *p = NULL;
SCTP_ERROR("Socket creation failed: %s\n", strerror(errno)); for (p = serv; p != NULL; p = p->ai_next) {
return; char buf[512];
const char *ip = print_ip(p, buf, sizeof(buf));
SCTP_DEBUG("Trying %s for client socket creation\n", ip);
if ((sd = socket(serv->ai_family, serv->ai_socktype, serv->ai_protocol)) == -1) {
SCTP_WARN("Socket creation failed: %s\n", strerror(errno));
continue;
} }
/* Add the socket to list of fd monitored by ITTI */ /* we assume we can set options, or something is likely broken; the
itti_subscribe_event_fd(TASK_SCTP, sd); * connection won't operate properly */
int ret = sctp_set_init_opt(sd, req->in_streams, req->out_streams, SCTP_MAX_ATTEMPTS, SCTP_TIMEOUT);
if (sctp_set_init_opt(sd, AssertFatal(ret == 0, "sctp_set_init_opt() failed\n");
sctp_new_association_req_p->in_streams,
sctp_new_association_req_p->out_streams,
SCTP_MAX_ATTEMPTS, SCTP_TIMEOUT) != 0) {
SCTP_ERROR("Setsockopt IPPROTO_SCTP_INITMSG failed: %s\n",
strerror(errno));
itti_unsubscribe_event_fd(TASK_SCTP, sd);
close(sd);
return;
}
/* Subscribe to all events */ /* Subscribe to all events */
struct sctp_event_subscribe events = {0};
events.sctp_data_io_event = 1; events.sctp_data_io_event = 1;
events.sctp_association_event = 1; events.sctp_association_event = 1;
events.sctp_address_event = 1; events.sctp_address_event = 1;
...@@ -421,190 +423,104 @@ sctp_handle_new_association_req( ...@@ -421,190 +423,104 @@ sctp_handle_new_association_req(
events.sctp_shutdown_event = 1; events.sctp_shutdown_event = 1;
events.sctp_partial_delivery_event = 1; events.sctp_partial_delivery_event = 1;
if (setsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, &events, /* as above */
8) < 0) { ret = setsockopt(sd, serv->ai_protocol, SCTP_EVENTS, &events, 8);
SCTP_ERROR("Setsockopt IPPROTO_SCTP_EVENTS failed: %s\n", AssertFatal(ret == 0, "setsockopt() IPPROTO_SCTP_EVENTS failed: %s\n", strerror(errno));
strerror(errno));
close(sd);
return;
}
// Bind to device ... or we could bind to address also /* if that fails, we will try the next address */
if (getifaddrs(&ifaddr) == -1) { ret = sctp_bindx(sd, p->ai_addr, 1, SCTP_BINDX_ADD_ADDR);
SCTP_ERROR("getifaddrs failed: %s\n", strerror(errno)); if (ret != 0) {
SCTP_WARN("sctp_bindx() SCTP_BINDX_ADD_ADDR failed: errno %d %s\n", errno, strerror(errno));
close(sd); close(sd);
}
/* Walk through linked list, maintaining head pointer so we
can free list later */
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue; continue;
family = ifa->ifa_addr->sa_family;
/* For an AF_INET* interface address, display the address */
if (sctp_new_association_req_p->local_address.ipv4 && family == AF_INET) {
// compare address
s = inet_aton(sctp_new_association_req_p->local_address.ipv4_address,
&in);
if (s > 0 ) {
if (((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr == in.s_addr) {
struct sockaddr_in locaddr={0};
locaddr.sin_family = AF_INET;
locaddr.sin_port = 0;
locaddr.sin_addr.s_addr = in.s_addr;
if (sctp_bindx(sd, (struct sockaddr*)&locaddr, 1, SCTP_BINDX_ADD_ADDR) < 0) {
SCTP_ERROR("sctp_bindx SCTP_BINDX_ADD_ADDR failed: %s\n",
strerror(errno));
} else {
SCTP_DEBUG("sctp_bindx SCTP_BINDX_ADD_ADDR socket bound to : %s\n",
inet_ntoa(locaddr.sin_addr));
}
break;
}
}
} else if (sctp_new_association_req_p->local_address.ipv6 && family == AF_INET6) {
// compare address
s = inet_pton(AF_INET6,
sctp_new_association_req_p->local_address.ipv6_address,
&in6);
if (s == 1 ) {
if (memcmp(&((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr,
&in6, sizeof(in6)) == 0) {
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifa->ifa_name);
if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
SCTP_ERROR("Setsockopt SOL_SOCKET failed: %s\n",
strerror(errno));
} else {
SCTP_DEBUG("Setsockopt SOL_SOCKET socket bound to : %s\n",
ifa->ifa_name);
} }
SCTP_DEBUG("sctp_bindx() SCTP_BINDX_ADD_ADDR: socket bound to %s/%s\n", local, ip);
break; break;
} }
}
}
}
freeifaddrs(ifaddr); freeaddrinfo(serv);
/* Mark the socket as non-blocking */ if (p == NULL) {
if (fcntl(sd, F_SETFL, O_NONBLOCK) < 0) { SCTP_ERROR("could not open socket, no SCTP connection established\n");
SCTP_ERROR("fcntl F_SETFL O_NONBLOCK failed: %s\n",
strerror(errno));
close(sd);
return; return;
} }
/* SOCK_STREAM socket type requires an explicit connect to the remote host /* SOCK_STREAM socket type requires an explicit connect to the remote host
* address and port. * address and port. */
* Only use IPv4 for the first connection attempt /* remote address: IPv4 has priority, but can also handle IPv6 */
*/ const char *remote = NULL;
if ((sctp_new_association_req_p->remote_address.ipv6 != 0) || if (req->remote_address.ipv6) {
(sctp_new_association_req_p->remote_address.ipv4 != 0)) { remote = req->remote_address.ipv6_address;
uint8_t address_index = 0; SCTP_WARN("please specify IPv6 addresses in the IPv4 field, IPv4 handles both\n");
uint8_t used_address = sctp_new_association_req_p->remote_address.ipv6 + }
sctp_new_association_req_p->remote_address.ipv4; if (req->remote_address.ipv4)
struct sockaddr_in addr[used_address]; remote = req->remote_address.ipv4_address;
sctp_assoc_t assoc_id = 0;
memset(addr, 0, used_address * sizeof(struct sockaddr_in)); if (remote != NULL) {
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_SCTP};
if (sctp_new_association_req_p->remote_address.ipv6 == 1) { struct addrinfo *serv;
if (inet_pton(AF_INET6, sctp_new_association_req_p->remote_address.ipv6_address,
&addr[address_index].sin_addr.s_addr) != 1) { char port[12];
SCTP_ERROR("Failed to convert ipv6 address %*s to network type\n", snprintf(port, sizeof(port), "%d", req->port);
(int)strlen(sctp_new_association_req_p->remote_address.ipv6_address), int status = getaddrinfo(remote, port, &hints, &serv);
sctp_new_association_req_p->remote_address.ipv6_address); AssertFatal(status == 0, "getaddrinfo() failed: %s\n", gai_strerror(status));
close(sd);
return; struct addrinfo *p = NULL;
} for (p = serv; p != NULL; p = p->ai_next) {
char buf[512];
SCTP_DEBUG("Converted ipv6 address %*s to network type\n", const char *ip = print_ip(p, buf, sizeof(buf));
(int)strlen(sctp_new_association_req_p->remote_address.ipv6_address), SCTP_DEBUG("Trying to connect to %s for remote end %s\n", ip, remote);
sctp_new_association_req_p->remote_address.ipv6_address);
if (sctp_connectx(sd, p->ai_addr, 1, &assoc_id) < 0) {
addr[address_index].sin_family = AF_INET6;
addr[address_index].sin_port = htons(sctp_new_association_req_p->port);
address_index++;
}
if (sctp_new_association_req_p->remote_address.ipv4 == 1) {
if (inet_pton(AF_INET, sctp_new_association_req_p->remote_address.ipv4_address,
&addr[address_index].sin_addr.s_addr) != 1) {
SCTP_ERROR("Failed to convert ipv4 address %*s to network type\n",
(int)strlen(sctp_new_association_req_p->remote_address.ipv4_address),
sctp_new_association_req_p->remote_address.ipv4_address);
close(sd);
return;
}
SCTP_DEBUG("Converted ipv4 address %*s to network type\n",
(int)strlen(sctp_new_association_req_p->remote_address.ipv4_address),
sctp_new_association_req_p->remote_address.ipv4_address);
addr[address_index].sin_family = AF_INET;
addr[address_index].sin_port = htons(sctp_new_association_req_p->port);
address_index++;
}
/* Connect to remote host and port */
if (sctp_connectx(sd, (struct sockaddr *)addr, 1, &assoc_id) < 0) {
/* sctp_connectx on non-blocking socket return EINPROGRESS */ /* sctp_connectx on non-blocking socket return EINPROGRESS */
if (errno != EINPROGRESS) { if (errno != EINPROGRESS) {
SCTP_ERROR("Connect failed: %s\n", strerror(errno)); SCTP_ERROR("Connect failed: %s\n", strerror(errno));
sctp_itti_send_association_resp( sctp_itti_send_association_resp(requestor, instance, -1, req->ulp_cnx_id, SCTP_STATE_UNREACHABLE, 0, 0);
requestor, instance, -1, sctp_new_association_req_p->ulp_cnx_id,
SCTP_STATE_UNREACHABLE, 0, 0);
/* Add the socket to list of fd monitored by ITTI */
itti_unsubscribe_event_fd(TASK_SCTP, sd);
close(sd); close(sd);
return; return;
} else { } else {
SCTP_DEBUG("connectx assoc_id %d in progress..., used %d addresses\n", SCTP_DEBUG("sctp_connectx(): assoc_id %d in progress...\n", assoc_id);
assoc_id, used_address);
} }
} else { } else {
SCTP_DEBUG("sctp_connectx SUCCESS, used %d addresses assoc_id %d\n", SCTP_DEBUG("sctp_connectx() SUCCESS: used assoc_id %d\n", assoc_id);
used_address, }
assoc_id); break;
} }
freeaddrinfo(serv);
} else { } else {
/* I am not sure that this is relevant; we already did sctp_bindx() above */
connection_type = SCTP_TYPE_SERVER;
/* No remote address provided -> only bind the socket for now. /* No remote address provided -> only bind the socket for now.
* Connection will be accepted in the main event loop * Connection will be accepted in the main event loop
*/ */
/*
struct sockaddr_in6 addr6; struct sockaddr_in6 addr6;
connection_type = SCTP_TYPE_SERVER;
/* For now bind to any interface */
addr6.sin6_family = AF_INET6; addr6.sin6_family = AF_INET6;
addr6.sin6_addr = in6addr_any; addr6.sin6_addr = in6addr_any;
addr6.sin6_port = htons(sctp_new_association_req_p->port); addr6.sin6_port = htons(req->port);
addr6.sin6_flowinfo = 0; addr6.sin6_flowinfo = 0;
if (bind(sd, (struct sockaddr*)&addr6, sizeof(addr6)) < 0) { if (bind(sd, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
SCTP_ERROR("Failed to bind the socket to address any (v4/v6): %s\n", SCTP_ERROR("Failed to bind the socket to address any (v4/v6): %s\n", strerror(errno));
strerror(errno));
close(sd); close(sd);
return; return;
} }
*/
} }
sctp_cnx = calloc(1, sizeof(*sctp_cnx)); struct sctp_cnx_list_elm_s *sctp_cnx = calloc(1, sizeof(*sctp_cnx));
AssertFatal(sctp_cnx != NULL, "out of memory\n");
sctp_cnx->connection_type = connection_type; sctp_cnx->connection_type = connection_type;
sctp_cnx->sd = sd; sctp_cnx->sd = sd;
sctp_cnx->task_id = requestor; sctp_cnx->task_id = requestor;
sctp_cnx->cnx_id = sctp_new_association_req_p->ulp_cnx_id; sctp_cnx->cnx_id = req->ulp_cnx_id;
sctp_cnx->ppid = sctp_new_association_req_p->ppid; sctp_cnx->ppid = req->ppid;
sctp_cnx->instance = instance; sctp_cnx->instance = instance;
sctp_cnx->assoc_id = assoc_id; sctp_cnx->assoc_id = assoc_id;
...@@ -612,8 +528,10 @@ sctp_handle_new_association_req( ...@@ -612,8 +528,10 @@ sctp_handle_new_association_req(
STAILQ_INSERT_TAIL(&sctp_cnx_list, sctp_cnx, entries); STAILQ_INSERT_TAIL(&sctp_cnx_list, sctp_cnx, entries);
sctp_nb_cnx++; sctp_nb_cnx++;
SCTP_DEBUG("Inserted new descriptor for sd %d in list, nb elements %u, assoc_id %d\n", /* Add the socket to list of fd monitored by ITTI */
sd, sctp_nb_cnx, assoc_id); itti_subscribe_event_fd(TASK_SCTP, sd);
SCTP_DEBUG("Inserted new descriptor for sd %d in list, nb elements %u, assoc_id %d\n", sd, sctp_nb_cnx, assoc_id);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment