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(
ns, sctp_nb_cnx, assoc_id);
}
//------------------------------------------------------------------------------
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)
static const char *print_ip(struct addrinfo *p, char *buf, size_t buf_len)
{
int sd = 0;
sctp_assoc_t assoc_id = 0;
struct sctp_event_subscribe events={0};
struct sctp_cnx_list_elm_s *sctp_cnx = NULL;
enum sctp_connection_type_e connection_type = SCTP_TYPE_CLIENT;
struct ifreq ifr;
struct ifaddrs *ifaddr = NULL;
struct ifaddrs *ifa = NULL;
int family = 0;
int s = 0;
struct in_addr in;
struct in6_addr in6;
/* Prepare a new SCTP association as requested by upper layer and try to connect
* to remote host.
*/
DevAssert(sctp_new_association_req_p != NULL);
void *addr;
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;
}
// convert the IP to a string and print it:
return inet_ntop(p->ai_family, addr, buf, buf_len);
}
/* Create new socket with IPv6 affinity */
//#warning "SCTP may Force IPv4 only, here"
#ifdef NO_VIRTUAL_MACHINE
// in init chunk appears a list of host addresses, IPv4 and IPv4 in an arbitrary (unsorted) order
// SCTP hearbeats starts with first ipv4 addresses then stop triyng with other ipv4 addresses
// 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) {
#endif
SCTP_ERROR("Socket creation failed: %s\n", strerror(errno));
return;
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;
/* local address: IPv4 has priority, but can also handle IPv6 */
const char *local = NULL;
if (req->local_address.ipv6) {
local = req->local_address.ipv6_address;
SCTP_WARN("please specify IPv6 addresses in the IPv4 field, IPv4 handles both\n");
}
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
* to remote host.
*/
DevAssert(req != NULL);
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_SCTP};
if (local == NULL)
hints.ai_flags = AI_PASSIVE;
struct addrinfo *serv;
int status = getaddrinfo(local, NULL, &hints, &serv);
AssertFatal(status == 0, "getaddrinfo() failed: %s\n", gai_strerror(status));
int sd;
struct addrinfo *p = NULL;
for (p = serv; p != NULL; p = p->ai_next) {
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 */
itti_subscribe_event_fd(TASK_SCTP, sd);
if (sctp_set_init_opt(sd,
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;
}
/* we assume we can set options, or something is likely broken; the
* connection won't operate properly */
int ret = sctp_set_init_opt(sd, req->in_streams, req->out_streams, SCTP_MAX_ATTEMPTS, SCTP_TIMEOUT);
AssertFatal(ret == 0, "sctp_set_init_opt() failed\n");
/* Subscribe to all events */
struct sctp_event_subscribe events = {0};
events.sctp_data_io_event = 1;
events.sctp_association_event = 1;
events.sctp_address_event = 1;
......@@ -421,199 +423,115 @@ sctp_handle_new_association_req(
events.sctp_shutdown_event = 1;
events.sctp_partial_delivery_event = 1;
if (setsockopt(sd, IPPROTO_SCTP, SCTP_EVENTS, &events,
8) < 0) {
SCTP_ERROR("Setsockopt IPPROTO_SCTP_EVENTS failed: %s\n",
strerror(errno));
close(sd);
return;
}
/* as above */
ret = setsockopt(sd, serv->ai_protocol, SCTP_EVENTS, &events, 8);
AssertFatal(ret == 0, "setsockopt() IPPROTO_SCTP_EVENTS failed: %s\n", strerror(errno));
// Bind to device ... or we could bind to address also
if (getifaddrs(&ifaddr) == -1) {
SCTP_ERROR("getifaddrs failed: %s\n", strerror(errno));
close(sd);
/* if that fails, we will try the next address */
ret = sctp_bindx(sd, p->ai_addr, 1, SCTP_BINDX_ADD_ADDR);
if (ret != 0) {
SCTP_WARN("sctp_bindx() SCTP_BINDX_ADD_ADDR failed: errno %d %s\n", errno, strerror(errno));
close(sd);
continue;
}
/* 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;
SCTP_DEBUG("sctp_bindx() SCTP_BINDX_ADD_ADDR: socket bound to %s/%s\n", local, ip);
break;
}
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;
freeaddrinfo(serv);
}
}
} 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);
}
break;
}
}
if (p == NULL) {
SCTP_ERROR("could not open socket, no SCTP connection established\n");
return;
}
/* SOCK_STREAM socket type requires an explicit connect to the remote host
* address and port. */
/* remote address: IPv4 has priority, but can also handle IPv6 */
const char *remote = NULL;
if (req->remote_address.ipv6) {
remote = req->remote_address.ipv6_address;
SCTP_WARN("please specify IPv6 addresses in the IPv4 field, IPv4 handles both\n");
}
if (req->remote_address.ipv4)
remote = req->remote_address.ipv4_address;
sctp_assoc_t assoc_id = 0;
if (remote != NULL) {
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_SCTP};
struct addrinfo *serv;
char port[12];
snprintf(port, sizeof(port), "%d", req->port);
int status = getaddrinfo(remote, port, &hints, &serv);
AssertFatal(status == 0, "getaddrinfo() failed: %s\n", gai_strerror(status));
struct addrinfo *p = NULL;
for (p = serv; p != NULL; p = p->ai_next) {
char buf[512];
const char *ip = print_ip(p, buf, sizeof(buf));
SCTP_DEBUG("Trying to connect to %s for remote end %s\n", ip, remote);
if (sctp_connectx(sd, p->ai_addr, 1, &assoc_id) < 0) {
/* sctp_connectx on non-blocking socket return EINPROGRESS */
if (errno != EINPROGRESS) {
SCTP_ERROR("Connect failed: %s\n", strerror(errno));
sctp_itti_send_association_resp(requestor, instance, -1, req->ulp_cnx_id, SCTP_STATE_UNREACHABLE, 0, 0);
close(sd);
return;
} else {
SCTP_DEBUG("sctp_connectx(): assoc_id %d in progress...\n", assoc_id);
}
} else {
SCTP_DEBUG("sctp_connectx() SUCCESS: used assoc_id %d\n", assoc_id);
}
break;
}
freeifaddrs(ifaddr);
/* Mark the socket as non-blocking */
if (fcntl(sd, F_SETFL, O_NONBLOCK) < 0) {
SCTP_ERROR("fcntl F_SETFL O_NONBLOCK failed: %s\n",
strerror(errno));
close(sd);
return;
}
freeaddrinfo(serv);
} else {
/* I am not sure that this is relevant; we already did sctp_bindx() above */
connection_type = SCTP_TYPE_SERVER;
/* SOCK_STREAM socket type requires an explicit connect to the remote host
* address and port.
* Only use IPv4 for the first connection attempt
/* No remote address provided -> only bind the socket for now.
* Connection will be accepted in the main event loop
*/
if ((sctp_new_association_req_p->remote_address.ipv6 != 0) ||
(sctp_new_association_req_p->remote_address.ipv4 != 0)) {
uint8_t address_index = 0;
uint8_t used_address = sctp_new_association_req_p->remote_address.ipv6 +
sctp_new_association_req_p->remote_address.ipv4;
struct sockaddr_in addr[used_address];
/*
struct sockaddr_in6 addr6;
memset(addr, 0, used_address * sizeof(struct sockaddr_in));
if (sctp_new_association_req_p->remote_address.ipv6 == 1) {
if (inet_pton(AF_INET6, sctp_new_association_req_p->remote_address.ipv6_address,
&addr[address_index].sin_addr.s_addr) != 1) {
SCTP_ERROR("Failed to convert ipv6 address %*s to network type\n",
(int)strlen(sctp_new_association_req_p->remote_address.ipv6_address),
sctp_new_association_req_p->remote_address.ipv6_address);
close(sd);
return;
}
addr6.sin6_family = AF_INET6;
addr6.sin6_addr = in6addr_any;
addr6.sin6_port = htons(req->port);
addr6.sin6_flowinfo = 0;
SCTP_DEBUG("Converted ipv6 address %*s to network type\n",
(int)strlen(sctp_new_association_req_p->remote_address.ipv6_address),
sctp_new_association_req_p->remote_address.ipv6_address);
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 */
if (errno != EINPROGRESS) {
SCTP_ERROR("Connect failed: %s\n", strerror(errno));
sctp_itti_send_association_resp(
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);
return;
} else {
SCTP_DEBUG("connectx assoc_id %d in progress..., used %d addresses\n",
assoc_id, used_address);
}
} else {
SCTP_DEBUG("sctp_connectx SUCCESS, used %d addresses assoc_id %d\n",
used_address,
assoc_id);
}
} else {
/* No remote address provided -> only bind the socket for now.
* Connection will be accepted in the main event loop
*/
struct sockaddr_in6 addr6;
connection_type = SCTP_TYPE_SERVER;
/* For now bind to any interface */
addr6.sin6_family = AF_INET6;
addr6.sin6_addr = in6addr_any;
addr6.sin6_port = htons(sctp_new_association_req_p->port);
addr6.sin6_flowinfo = 0;
if (bind(sd, (struct sockaddr*)&addr6, sizeof(addr6)) < 0) {
SCTP_ERROR("Failed to bind the socket to address any (v4/v6): %s\n",
strerror(errno));
close(sd);
return;
}
if (bind(sd, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
SCTP_ERROR("Failed to bind the socket to address any (v4/v6): %s\n", strerror(errno));
close(sd);
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->task_id = requestor;
sctp_cnx->cnx_id = req->ulp_cnx_id;
sctp_cnx->ppid = req->ppid;
sctp_cnx->instance = instance;
sctp_cnx->assoc_id = assoc_id;
sctp_cnx->sd = sd;
sctp_cnx->task_id = requestor;
sctp_cnx->cnx_id = sctp_new_association_req_p->ulp_cnx_id;
sctp_cnx->ppid = sctp_new_association_req_p->ppid;
sctp_cnx->instance = instance;
sctp_cnx->assoc_id = assoc_id;
/* Insert new element at end of list */
STAILQ_INSERT_TAIL(&sctp_cnx_list, sctp_cnx, entries);
sctp_nb_cnx++;
/* Insert new element at end of list */
STAILQ_INSERT_TAIL(&sctp_cnx_list, sctp_cnx, entries);
sctp_nb_cnx++;
/* Add the socket to list of fd monitored by ITTI */
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);
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