#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/stropts.h>
#include <sys/socket.h>
#include <sys/random.h>
#include <sys/policy.h>
#include <sys/tsol/tndb.h>
#include <sys/tsol/tnet.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ipclassifier.h>
#include "sctp_impl.h"
#include "sctp_asconf.h"
#include "sctp_addr.h"
static uint32_t sctp_min_assoc_listener = 2;
static int
sctp_select_port(sctp_t *sctp, in_port_t *requested_port, int *user_specified)
{
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
if (*requested_port == 0) {
*requested_port = sctp_update_next_port(
sctps->sctps_next_port_to_try,
crgetzone(connp->conn_cred), sctps);
if (*requested_port == 0)
return (EACCES);
*user_specified = 0;
} else {
int i;
boolean_t priv = B_FALSE;
if (*requested_port < sctps->sctps_smallest_nonpriv_port) {
priv = B_TRUE;
} else {
for (i = 0; i < sctps->sctps_g_num_epriv_ports; i++) {
if (*requested_port ==
sctps->sctps_g_epriv_ports[i]) {
priv = B_TRUE;
break;
}
}
}
if (priv) {
if (secpolicy_net_privaddr(connp->conn_cred,
*requested_port, IPPROTO_SCTP) != 0) {
dprint(1,
("sctp_bind(x): no prive for port %d",
*requested_port));
return (EACCES);
}
}
*user_specified = 1;
}
return (0);
}
int
sctp_listen(sctp_t *sctp)
{
sctp_tf_t *tf;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
RUN_SCTP(sctp);
if (sctp->sctp_state > SCTPS_BOUND ||
(sctp->sctp_connp->conn_state_flags & CONN_CLOSING)) {
WAKE_SCTP(sctp);
return (EINVAL);
}
if (sctp->sctp_nsaddrs == 0) {
struct sockaddr_storage ss;
int ret;
bzero(&ss, sizeof (ss));
ss.ss_family = connp->conn_family;
WAKE_SCTP(sctp);
if ((ret = sctp_bind(sctp, (struct sockaddr *)&ss,
sizeof (ss))) != 0)
return (ret);
RUN_SCTP(sctp)
}
ASSERT(!(connp->conn_ixa->ixa_free_flags & IXA_FREE_CRED));
connp->conn_ixa->ixa_cred = connp->conn_cred;
connp->conn_ixa->ixa_cpid = connp->conn_cpid;
if (is_system_labeled())
connp->conn_ixa->ixa_tsl = crgetlabel(connp->conn_cred);
sctp->sctp_state = SCTPS_LISTEN;
(void) random_get_pseudo_bytes(sctp->sctp_secret, SCTP_SECRET_LEN);
sctp->sctp_last_secret_update = ddi_get_lbolt64();
bzero(sctp->sctp_old_secret, SCTP_SECRET_LEN);
if (!list_is_empty(&sctps->sctps_listener_conf) &&
sctp->sctp_listen_cnt == NULL) {
sctp_listen_cnt_t *slc;
uint32_t ratio;
ratio = sctp_find_listener_conf(sctps,
ntohs(connp->conn_lport));
if (ratio != 0) {
uint32_t mem_ratio, tot_buf;
slc = kmem_alloc(sizeof (sctp_listen_cnt_t), KM_SLEEP);
if ((tot_buf = connp->conn_rcvbuf +
connp->conn_sndbuf) < MB) {
mem_ratio = MB / tot_buf;
slc->slc_max = maxusers / ratio * mem_ratio;
} else {
mem_ratio = tot_buf / MB;
slc->slc_max = maxusers / ratio / mem_ratio;
}
if (slc->slc_max < sctp_min_assoc_listener)
slc->slc_max = sctp_min_assoc_listener;
slc->slc_cnt = 1;
slc->slc_drop = 0;
sctp->sctp_listen_cnt = slc;
}
}
tf = &sctps->sctps_listen_fanout[SCTP_LISTEN_HASH(
ntohs(connp->conn_lport))];
sctp_listen_hash_insert(tf, sctp);
WAKE_SCTP(sctp);
return (0);
}
int
sctp_bind(sctp_t *sctp, struct sockaddr *sa, socklen_t len)
{
int user_specified;
boolean_t bind_to_req_port_only;
in_port_t requested_port;
in_port_t allocated_port;
int err = 0;
conn_t *connp = sctp->sctp_connp;
uint_t scope_id;
sin_t *sin;
sin6_t *sin6;
ASSERT(sctp != NULL);
RUN_SCTP(sctp);
if ((sctp->sctp_state >= SCTPS_BOUND) ||
(sctp->sctp_connp->conn_state_flags & CONN_CLOSING) ||
(sa == NULL || len == 0)) {
err = EINVAL;
goto done;
}
switch (sa->sa_family) {
case AF_INET:
sin = (sin_t *)sa;
if (len < sizeof (struct sockaddr_in) ||
connp->conn_family == AF_INET6) {
err = EINVAL;
goto done;
}
requested_port = ntohs(sin->sin_port);
break;
case AF_INET6:
sin6 = (sin6_t *)sa;
if (len < sizeof (struct sockaddr_in6) ||
connp->conn_family == AF_INET) {
err = EINVAL;
goto done;
}
requested_port = ntohs(sin6->sin6_port);
connp->conn_flowinfo =
sin6->sin6_flowinfo & ~IPV6_VERS_AND_FLOW_MASK;
scope_id = sin6->sin6_scope_id;
if (scope_id != 0 && IN6_IS_ADDR_LINKSCOPE(&sin6->sin6_addr)) {
connp->conn_ixa->ixa_flags |= IXAF_SCOPEID_SET;
connp->conn_ixa->ixa_scopeid = scope_id;
connp->conn_incoming_ifindex = scope_id;
} else {
connp->conn_ixa->ixa_flags &= ~IXAF_SCOPEID_SET;
connp->conn_incoming_ifindex = connp->conn_bound_if;
}
break;
default:
err = EAFNOSUPPORT;
goto done;
}
bind_to_req_port_only = requested_port == 0 ? B_FALSE : B_TRUE;
err = sctp_select_port(sctp, &requested_port, &user_specified);
if (err != 0)
goto done;
if ((err = sctp_bind_add(sctp, sa, 1, B_TRUE,
user_specified == 1 ? htons(requested_port) : 0)) != 0) {
goto done;
}
err = sctp_bindi(sctp, requested_port, bind_to_req_port_only,
user_specified, &allocated_port);
if (err != 0) {
sctp_free_saddrs(sctp);
} else {
ASSERT(sctp->sctp_state == SCTPS_BOUND);
}
done:
WAKE_SCTP(sctp);
return (err);
}
int
sctp_bindx(sctp_t *sctp, const void *addrs, int addrcnt, int bindop)
{
ASSERT(sctp != NULL);
ASSERT(addrs != NULL);
ASSERT(addrcnt > 0);
switch (bindop) {
case SCTP_BINDX_ADD_ADDR:
return (sctp_bind_add(sctp, addrs, addrcnt, B_FALSE,
sctp->sctp_connp->conn_lport));
case SCTP_BINDX_REM_ADDR:
return (sctp_bind_del(sctp, addrs, addrcnt, B_FALSE));
default:
return (EINVAL);
}
}
int
sctp_bind_add(sctp_t *sctp, const void *addrs, uint32_t addrcnt,
boolean_t caller_hold_lock, in_port_t port)
{
int err = 0;
boolean_t do_asconf = B_FALSE;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
if (!caller_hold_lock)
RUN_SCTP(sctp);
if (sctp->sctp_state > SCTPS_ESTABLISHED ||
(sctp->sctp_connp->conn_state_flags & CONN_CLOSING)) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
if (sctp->sctp_state > SCTPS_LISTEN) {
if (!sctps->sctps_addip_enabled ||
!sctp->sctp_understands_asconf ||
!sctp->sctp_understands_addip) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
do_asconf = B_TRUE;
}
if (cl_sctp_check_addrs != NULL) {
uchar_t *addrlist = NULL;
size_t size = 0;
int unspec = 0;
boolean_t do_listen;
uchar_t *llist = NULL;
size_t lsize = 0;
do_listen = !do_asconf && sctp->sctp_state > SCTPS_BOUND &&
cl_sctp_listen != NULL;
err = sctp_get_addrlist(sctp, addrs, &addrcnt, &addrlist,
&unspec, &size);
if (err != 0) {
ASSERT(addrlist == NULL);
ASSERT(addrcnt == 0);
ASSERT(size == 0);
if (!caller_hold_lock)
WAKE_SCTP(sctp);
SCTP_KSTAT(sctps, sctp_cl_check_addrs);
return (err);
}
ASSERT(addrlist != NULL);
(*cl_sctp_check_addrs)(connp->conn_family, port, &addrlist,
size, &addrcnt, unspec == 1);
if (addrcnt == 0) {
kmem_free(addrlist, size);
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
if (do_listen) {
lsize = sizeof (in6_addr_t) * addrcnt;
llist = kmem_alloc(lsize, KM_SLEEP);
}
err = sctp_valid_addr_list(sctp, addrlist, addrcnt, llist,
lsize);
if (err == 0 && do_listen) {
(*cl_sctp_listen)(connp->conn_family, llist,
addrcnt, connp->conn_lport);
} else if (err != 0 && llist != NULL) {
kmem_free(llist, lsize);
}
kmem_free(addrlist, size);
} else {
err = sctp_valid_addr_list(sctp, addrs, addrcnt, NULL, 0);
}
if (err != 0) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (err);
}
if (do_asconf) {
err = sctp_add_ip(sctp, addrs, addrcnt);
if (err != 0) {
sctp_del_saddr_list(sctp, addrs, addrcnt, B_FALSE);
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (err);
}
}
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (0);
}
int
sctp_bind_del(sctp_t *sctp, const void *addrs, uint32_t addrcnt,
boolean_t caller_hold_lock)
{
int error = 0;
boolean_t do_asconf = B_FALSE;
uchar_t *ulist = NULL;
size_t usize = 0;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
if (!caller_hold_lock)
RUN_SCTP(sctp);
if (sctp->sctp_state > SCTPS_ESTABLISHED ||
(sctp->sctp_connp->conn_state_flags & CONN_CLOSING)) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
if (sctp->sctp_state > SCTPS_LISTEN) {
if (!sctps->sctps_addip_enabled ||
!sctp->sctp_understands_asconf ||
!sctp->sctp_understands_addip) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
do_asconf = B_TRUE;
}
if (sctp->sctp_nsaddrs == 1 || addrcnt >= sctp->sctp_nsaddrs) {
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (EINVAL);
}
if (cl_sctp_unlisten != NULL && !do_asconf &&
sctp->sctp_state > SCTPS_BOUND) {
usize = sizeof (in6_addr_t) * addrcnt;
ulist = kmem_alloc(usize, KM_SLEEP);
}
error = sctp_del_ip(sctp, addrs, addrcnt, ulist, usize);
if (error != 0) {
if (ulist != NULL)
kmem_free(ulist, usize);
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (error);
}
if (ulist != NULL) {
ASSERT(cl_sctp_unlisten != NULL);
(*cl_sctp_unlisten)(connp->conn_family, ulist, addrcnt,
connp->conn_lport);
}
if (!caller_hold_lock)
WAKE_SCTP(sctp);
return (error);
}
int
sctp_bindi(sctp_t *sctp, in_port_t port, boolean_t bind_to_req_port_only,
int user_specified, in_port_t *allocated_port)
{
int count = 0;
int loopmax;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
zone_t *zone = crgetzone(connp->conn_cred);
zoneid_t zoneid = connp->conn_zoneid;
if (bind_to_req_port_only) {
loopmax = 1;
} else {
loopmax = (sctps->sctps_largest_anon_port -
sctps->sctps_smallest_anon_port + 1);
}
do {
uint16_t lport;
sctp_tf_t *tbf;
sctp_t *lsctp;
int addrcmp;
lport = htons(port);
sctp_bind_hash_remove(sctp);
tbf = &sctps->sctps_bind_fanout[SCTP_BIND_HASH(port)];
mutex_enter(&tbf->tf_lock);
for (lsctp = tbf->tf_sctp; lsctp != NULL;
lsctp = lsctp->sctp_bind_hash) {
conn_t *lconnp = lsctp->sctp_connp;
if (lport != lconnp->conn_lport ||
lsctp->sctp_state < SCTPS_BOUND)
continue;
if (lconnp->conn_zoneid != zoneid &&
lconnp->conn_mac_mode == CONN_MAC_DEFAULT &&
connp->conn_mac_mode == CONN_MAC_DEFAULT)
continue;
addrcmp = sctp_compare_saddrs(sctp, lsctp);
if (addrcmp != SCTP_ADDR_DISJOINT) {
if (!connp->conn_reuseaddr) {
break;
} else if (lsctp->sctp_state == SCTPS_BOUND ||
lsctp->sctp_state == SCTPS_LISTEN) {
break;
}
}
}
if (lsctp != NULL) {
mutex_exit(&tbf->tf_lock);
} else {
if (is_system_labeled()) {
mlp_type_t addrtype, mlptype;
uint_t ipversion;
if (connp->conn_family == AF_INET)
ipversion = IPV4_VERSION;
else
ipversion = IPV6_VERSION;
addrtype = tsol_mlp_addr_type(
connp->conn_allzones ? ALL_ZONES :
zone->zone_id,
ipversion,
connp->conn_family == AF_INET ?
(void *)&sctp->sctp_ipha->ipha_src :
(void *)&sctp->sctp_ip6h->ip6_src,
sctps->sctps_netstack->netstack_ip);
if (addrtype == mlptSingle) {
mutex_exit(&tbf->tf_lock);
return (EADDRNOTAVAIL);
}
mlptype = tsol_mlp_port_type(zone, IPPROTO_SCTP,
port, addrtype);
if (mlptype != mlptSingle) {
if (secpolicy_net_bindmlp(connp->
conn_cred) != 0) {
mutex_exit(&tbf->tf_lock);
return (EACCES);
}
if (mlptype == mlptShared &&
addrtype == mlptShared &&
connp->conn_zoneid !=
tsol_mlp_findzone(IPPROTO_SCTP,
lport)) {
mutex_exit(&tbf->tf_lock);
return (EACCES);
}
connp->conn_mlp_type = mlptype;
}
}
sctp->sctp_state = SCTPS_BOUND;
connp->conn_lport = lport;
ASSERT(&sctps->sctps_bind_fanout[
SCTP_BIND_HASH(port)] == tbf);
sctp_bind_hash_insert(tbf, sctp, 1);
mutex_exit(&tbf->tf_lock);
if (user_specified == 0)
sctps->sctps_next_port_to_try = port + 1;
*allocated_port = port;
return (0);
}
if ((count == 0) && (user_specified)) {
port = sctp_update_next_port(
sctps->sctps_next_port_to_try,
zone, sctps);
user_specified = 0;
} else {
port = sctp_update_next_port(port + 1, zone, sctps);
}
if (port == 0)
break;
} while (++count < loopmax);
return (bind_to_req_port_only ? EADDRINUSE : EADDRNOTAVAIL);
}
in_port_t
sctp_update_next_port(in_port_t port, zone_t *zone, sctp_stack_t *sctps)
{
int i;
boolean_t restart = B_FALSE;
retry:
if (port < sctps->sctps_smallest_anon_port)
port = sctps->sctps_smallest_anon_port;
if (port > sctps->sctps_largest_anon_port) {
if (restart)
return (0);
restart = B_TRUE;
port = sctps->sctps_smallest_anon_port;
}
if (port < sctps->sctps_smallest_nonpriv_port)
port = sctps->sctps_smallest_nonpriv_port;
for (i = 0; i < sctps->sctps_g_num_epriv_ports; i++) {
if (port == sctps->sctps_g_epriv_ports[i]) {
port++;
goto retry;
}
}
if (is_system_labeled() &&
(i = tsol_next_port(zone, port, IPPROTO_SCTP, B_TRUE)) != 0) {
port = i;
goto retry;
}
return (port);
}