#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
#include <sys/stropts.h>
#include <sys/strlog.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/suntpi.h>
#include <sys/xti_inet.h>
#include <sys/policy.h>
#include <sys/squeue_impl.h>
#include <sys/squeue.h>
#include <sys/tsol/tnet.h>
#include <rpc/pmap_prot.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/tcp.h>
#include <inet/tcp_impl.h>
#include <inet/proto_set.h>
#include <inet/ipsec_impl.h>
static uint32_t tcp_random_anon_port = 1;
static int tcp_bind_select_lport(tcp_t *, in_port_t *, boolean_t,
cred_t *cr);
static in_port_t tcp_get_next_priv_port(const tcp_t *);
void
tcp_bind_hash_insert(tf_t *tbf, tcp_t *tcp, int caller_holds_lock)
{
tcp_t **tcpp;
tcp_t *tcpnext;
tcp_t *tcphash;
conn_t *connp = tcp->tcp_connp;
conn_t *connext;
if (tcp->tcp_ptpbhn != NULL) {
ASSERT(!caller_holds_lock);
tcp_bind_hash_remove(tcp);
}
tcpp = &tbf->tf_tcp;
if (!caller_holds_lock) {
mutex_enter(&tbf->tf_lock);
} else {
ASSERT(MUTEX_HELD(&tbf->tf_lock));
}
tcphash = tcpp[0];
tcpnext = NULL;
if (tcphash != NULL) {
while ((tcphash = tcpp[0]) != NULL &&
connp->conn_lport != tcphash->tcp_connp->conn_lport)
tcpp = &(tcphash->tcp_bind_hash);
if (tcphash == NULL)
goto insert;
tcpnext = tcphash;
connext = tcpnext->tcp_connp;
tcphash = NULL;
if (V6_OR_V4_INADDR_ANY(connp->conn_bound_addr_v6) &&
!V6_OR_V4_INADDR_ANY(connext->conn_bound_addr_v6)) {
while ((tcpnext = tcpp[0]) != NULL) {
connext = tcpnext->tcp_connp;
if (!V6_OR_V4_INADDR_ANY(
connext->conn_bound_addr_v6))
tcpp = &(tcpnext->tcp_bind_hash_port);
else
break;
}
if (tcpnext != NULL) {
tcpnext->tcp_ptpbhn = &tcp->tcp_bind_hash_port;
tcphash = tcpnext->tcp_bind_hash;
if (tcphash != NULL) {
tcphash->tcp_ptpbhn =
&(tcp->tcp_bind_hash);
tcpnext->tcp_bind_hash = NULL;
}
}
} else {
tcpnext->tcp_ptpbhn = &tcp->tcp_bind_hash_port;
tcphash = tcpnext->tcp_bind_hash;
if (tcphash != NULL) {
tcphash->tcp_ptpbhn =
&(tcp->tcp_bind_hash);
tcpnext->tcp_bind_hash = NULL;
}
}
}
insert:
tcp->tcp_bind_hash_port = tcpnext;
tcp->tcp_bind_hash = tcphash;
tcp->tcp_ptpbhn = tcpp;
tcpp[0] = tcp;
if (!caller_holds_lock)
mutex_exit(&tbf->tf_lock);
}
void
tcp_bind_hash_remove(tcp_t *tcp)
{
tcp_t *tcpnext;
kmutex_t *lockp;
tcp_stack_t *tcps = tcp->tcp_tcps;
conn_t *connp = tcp->tcp_connp;
if (tcp->tcp_ptpbhn == NULL)
return;
ASSERT(connp->conn_lport != 0);
lockp = &tcps->tcps_bind_fanout[TCP_BIND_HASH(
connp->conn_lport)].tf_lock;
ASSERT(lockp != NULL);
mutex_enter(lockp);
if (tcp->tcp_ptpbhn) {
tcpnext = tcp->tcp_bind_hash_port;
if (tcpnext != NULL) {
tcp->tcp_bind_hash_port = NULL;
tcpnext->tcp_ptpbhn = tcp->tcp_ptpbhn;
tcpnext->tcp_bind_hash = tcp->tcp_bind_hash;
if (tcpnext->tcp_bind_hash != NULL) {
tcpnext->tcp_bind_hash->tcp_ptpbhn =
&(tcpnext->tcp_bind_hash);
tcp->tcp_bind_hash = NULL;
}
} else if ((tcpnext = tcp->tcp_bind_hash) != NULL) {
tcpnext->tcp_ptpbhn = tcp->tcp_ptpbhn;
tcp->tcp_bind_hash = NULL;
}
*tcp->tcp_ptpbhn = tcpnext;
tcp->tcp_ptpbhn = NULL;
}
mutex_exit(lockp);
}
in_port_t
tcp_update_next_port(in_port_t port, const tcp_t *tcp, boolean_t random)
{
int i, bump;
boolean_t restart = B_FALSE;
tcp_stack_t *tcps = tcp->tcp_tcps;
if (random && tcp_random_anon_port != 0) {
(void) random_get_pseudo_bytes((uint8_t *)&port,
sizeof (in_port_t));
if ((port < tcps->tcps_smallest_anon_port) ||
(port > tcps->tcps_largest_anon_port)) {
if (tcps->tcps_smallest_anon_port ==
tcps->tcps_largest_anon_port) {
bump = 0;
} else {
bump = port % (tcps->tcps_largest_anon_port -
tcps->tcps_smallest_anon_port);
}
port = tcps->tcps_smallest_anon_port + bump;
}
}
retry:
if (port < tcps->tcps_smallest_anon_port)
port = (in_port_t)tcps->tcps_smallest_anon_port;
if (port > tcps->tcps_largest_anon_port) {
if (restart)
return (0);
restart = B_TRUE;
port = (in_port_t)tcps->tcps_smallest_anon_port;
}
if (port < tcps->tcps_smallest_nonpriv_port)
port = (in_port_t)tcps->tcps_smallest_nonpriv_port;
for (i = 0; i < tcps->tcps_g_num_epriv_ports; i++) {
if (port == tcps->tcps_g_epriv_ports[i]) {
port++;
goto retry;
}
}
if (is_system_labeled() &&
(i = tsol_next_port(crgetzone(tcp->tcp_connp->conn_cred), port,
IPPROTO_TCP, B_TRUE)) != 0) {
port = i;
goto retry;
}
return (port);
}
static in_port_t
tcp_get_next_priv_port(const tcp_t *tcp)
{
static in_port_t next_priv_port = IPPORT_RESERVED - 1;
in_port_t nextport;
boolean_t restart = B_FALSE;
tcp_stack_t *tcps = tcp->tcp_tcps;
retry:
if (next_priv_port < tcps->tcps_min_anonpriv_port ||
next_priv_port >= IPPORT_RESERVED) {
next_priv_port = IPPORT_RESERVED - 1;
if (restart)
return (0);
restart = B_TRUE;
}
if (is_system_labeled() &&
(nextport = tsol_next_port(crgetzone(tcp->tcp_connp->conn_cred),
next_priv_port, IPPROTO_TCP, B_FALSE)) != 0) {
next_priv_port = nextport;
goto retry;
}
return (next_priv_port--);
}
static int
tcp_bind_select_lport(tcp_t *tcp, in_port_t *requested_port_ptr,
boolean_t bind_to_req_port_only, cred_t *cr)
{
in_port_t mlp_port;
mlp_type_t addrtype, mlptype;
boolean_t user_specified;
in_port_t allocated_port;
in_port_t requested_port = *requested_port_ptr;
conn_t *connp = tcp->tcp_connp;
zone_t *zone;
tcp_stack_t *tcps = tcp->tcp_tcps;
in6_addr_t v6addr = connp->conn_laddr_v6;
zone = NULL;
ASSERT(cr != NULL);
mlptype = mlptSingle;
mlp_port = requested_port;
if (requested_port == 0) {
requested_port = connp->conn_anon_priv_bind ?
tcp_get_next_priv_port(tcp) :
tcp_update_next_port(tcps->tcps_next_port_to_try,
tcp, B_TRUE);
if (requested_port == 0) {
return (-TNOADDR);
}
user_specified = B_FALSE;
if (connp->conn_anon_mlp && is_system_labeled()) {
zone = crgetzone(cr);
addrtype = tsol_mlp_addr_type(
connp->conn_allzones ? ALL_ZONES : zone->zone_id,
IPV6_VERSION, &v6addr,
tcps->tcps_netstack->netstack_ip);
if (addrtype == mlptSingle) {
return (-TNOADDR);
}
mlptype = tsol_mlp_port_type(zone, IPPROTO_TCP,
PMAPPORT, addrtype);
mlp_port = PMAPPORT;
}
} else {
int i;
boolean_t priv = B_FALSE;
if (requested_port < tcps->tcps_smallest_nonpriv_port) {
priv = B_TRUE;
} else {
for (i = 0; i < tcps->tcps_g_num_epriv_ports; i++) {
if (requested_port ==
tcps->tcps_g_epriv_ports[i]) {
priv = B_TRUE;
break;
}
}
}
if (priv) {
if (secpolicy_net_privaddr(cr, requested_port,
IPPROTO_TCP) != 0) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: no priv for port %d",
requested_port);
}
return (-TACCES);
}
}
user_specified = B_TRUE;
connp = tcp->tcp_connp;
if (is_system_labeled()) {
zone = crgetzone(cr);
addrtype = tsol_mlp_addr_type(
connp->conn_allzones ? ALL_ZONES : zone->zone_id,
IPV6_VERSION, &v6addr,
tcps->tcps_netstack->netstack_ip);
if (addrtype == mlptSingle) {
return (-TNOADDR);
}
mlptype = tsol_mlp_port_type(zone, IPPROTO_TCP,
requested_port, addrtype);
}
}
if (mlptype != mlptSingle) {
if (secpolicy_net_bindmlp(cr) != 0) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: no priv for multilevel port %d",
requested_port);
}
return (-TACCES);
}
if (mlptype == mlptShared && addrtype == mlptShared) {
zoneid_t mlpzone;
mlpzone = tsol_mlp_findzone(IPPROTO_TCP,
htons(mlp_port));
if (connp->conn_zoneid != mlpzone) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: attempt to bind port "
"%d on shared addr in zone %d "
"(should be %d)",
mlp_port, connp->conn_zoneid,
mlpzone);
}
return (-TACCES);
}
}
if (!user_specified) {
int err;
err = tsol_mlp_anon(zone, mlptype, connp->conn_proto,
requested_port, B_TRUE);
if (err != 0) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: cannot establish anon "
"MLP for port %d",
requested_port);
}
return (err);
}
connp->conn_anon_port = B_TRUE;
}
connp->conn_mlp_type = mlptype;
}
allocated_port = tcp_bindi(tcp, requested_port, &v6addr,
connp->conn_reuseaddr, B_FALSE, bind_to_req_port_only,
user_specified);
if (allocated_port == 0) {
connp->conn_mlp_type = mlptSingle;
if (connp->conn_anon_port) {
connp->conn_anon_port = B_FALSE;
(void) tsol_mlp_anon(zone, mlptype, connp->conn_proto,
requested_port, B_FALSE);
}
if (bind_to_req_port_only) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: requested addr busy");
}
return (-TADDRBUSY);
} else {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: out of ports?");
}
return (-TNOADDR);
}
}
*requested_port_ptr = allocated_port;
return (0);
}
int
tcp_bind_check(conn_t *connp, struct sockaddr *sa, socklen_t len, cred_t *cr,
boolean_t bind_to_req_port_only)
{
tcp_t *tcp = connp->conn_tcp;
sin_t *sin;
sin6_t *sin6;
in_port_t requested_port;
ipaddr_t v4addr;
in6_addr_t v6addr;
ip_laddr_t laddr_type = IPVL_UNICAST_UP;
zoneid_t zoneid = IPCL_ZONEID(connp);
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
uint_t scopeid = 0;
int error = 0;
ip_xmit_attr_t *ixa = connp->conn_ixa;
ASSERT((uintptr_t)len <= (uintptr_t)INT_MAX);
if (tcp->tcp_state == TCPS_BOUND) {
return (0);
} else if (tcp->tcp_state > TCPS_BOUND) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
"tcp_bind: bad state, %d", tcp->tcp_state);
}
return (-TOUTSTATE);
}
ASSERT(sa != NULL && len != 0);
if (!OK_32PTR((char *)sa)) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_bind: bad address parameter, "
"address %p, len %d",
(void *)sa, len);
}
return (-TPROTO);
}
error = proto_verify_ip_addr(connp->conn_family, sa, len);
if (error != 0) {
return (error);
}
switch (len) {
case sizeof (sin_t):
sin = (sin_t *)sa;
requested_port = ntohs(sin->sin_port);
v4addr = sin->sin_addr.s_addr;
IN6_IPADDR_TO_V4MAPPED(v4addr, &v6addr);
if (v4addr != INADDR_ANY) {
laddr_type = ip_laddr_verify_v4(v4addr, zoneid, ipst,
B_FALSE);
}
break;
case sizeof (sin6_t):
sin6 = (sin6_t *)sa;
v6addr = sin6->sin6_addr;
requested_port = ntohs(sin6->sin6_port);
if (IN6_IS_ADDR_V4MAPPED(&v6addr)) {
if (connp->conn_ipv6_v6only)
return (EADDRNOTAVAIL);
IN6_V4MAPPED_TO_IPADDR(&v6addr, v4addr);
if (v4addr != INADDR_ANY) {
laddr_type = ip_laddr_verify_v4(v4addr,
zoneid, ipst, B_FALSE);
}
} else {
if (!IN6_IS_ADDR_UNSPECIFIED(&v6addr)) {
if (IN6_IS_ADDR_LINKSCOPE(&v6addr))
scopeid = sin6->sin6_scope_id;
laddr_type = ip_laddr_verify_v6(&v6addr,
zoneid, ipst, B_FALSE, scopeid);
}
}
break;
default:
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
"tcp_bind: bad address length, %d", len);
}
return (EAFNOSUPPORT);
}
if (laddr_type == IPVL_BAD)
return (EADDRNOTAVAIL);
connp->conn_bound_addr_v6 = v6addr;
if (scopeid != 0) {
ixa->ixa_flags |= IXAF_SCOPEID_SET;
ixa->ixa_scopeid = scopeid;
connp->conn_incoming_ifindex = scopeid;
} else {
ixa->ixa_flags &= ~IXAF_SCOPEID_SET;
connp->conn_incoming_ifindex = connp->conn_bound_if;
}
connp->conn_laddr_v6 = v6addr;
connp->conn_saddr_v6 = v6addr;
bind_to_req_port_only = requested_port != 0 && bind_to_req_port_only;
error = tcp_bind_select_lport(tcp, &requested_port,
bind_to_req_port_only, cr);
if (error != 0) {
connp->conn_laddr_v6 = ipv6_all_zeros;
connp->conn_saddr_v6 = ipv6_all_zeros;
connp->conn_bound_addr_v6 = ipv6_all_zeros;
}
return (error);
}
in_port_t
tcp_bindi(tcp_t *tcp, in_port_t port, const in6_addr_t *laddr,
int reuseaddr, boolean_t quick_connect,
boolean_t bind_to_req_port_only, boolean_t user_specified)
{
int count = 0;
int loopmax;
conn_t *connp = tcp->tcp_connp;
tcp_stack_t *tcps = tcp->tcp_tcps;
if (bind_to_req_port_only) {
loopmax = 1;
} else {
if (connp->conn_anon_priv_bind) {
loopmax = IPPORT_RESERVED -
tcps->tcps_min_anonpriv_port;
} else {
loopmax = (tcps->tcps_largest_anon_port -
tcps->tcps_smallest_anon_port + 1);
}
}
do {
uint16_t lport;
tf_t *tbf;
tcp_t *ltcp;
conn_t *lconnp;
lport = htons(port);
tcp_bind_hash_remove(tcp);
tbf = &tcps->tcps_bind_fanout[TCP_BIND_HASH(lport)];
mutex_enter(&tbf->tf_lock);
for (ltcp = tbf->tf_tcp; ltcp != NULL;
ltcp = ltcp->tcp_bind_hash) {
if (lport == ltcp->tcp_connp->conn_lport)
break;
}
for (; ltcp != NULL; ltcp = ltcp->tcp_bind_hash_port) {
boolean_t not_socket;
boolean_t exclbind;
lconnp = ltcp->tcp_connp;
if (!IPCL_BIND_ZONE_MATCH(lconnp, connp))
continue;
if (connp->conn_incoming_ifindex !=
lconnp->conn_incoming_ifindex) {
if ((connp->conn_incoming_ifindex != 0) &&
(lconnp->conn_incoming_ifindex != 0))
continue;
}
not_socket = !(TCP_IS_SOCKET(ltcp) &&
TCP_IS_SOCKET(tcp));
exclbind = lconnp->conn_exclbind ||
connp->conn_exclbind;
if ((lconnp->conn_mac_mode != CONN_MAC_DEFAULT) ||
(connp->conn_mac_mode != CONN_MAC_DEFAULT) ||
(exclbind && (not_socket ||
ltcp->tcp_state <= TCPS_ESTABLISHED))) {
if (V6_OR_V4_INADDR_ANY(
lconnp->conn_bound_addr_v6) ||
V6_OR_V4_INADDR_ANY(*laddr) ||
IN6_ARE_ADDR_EQUAL(laddr,
&lconnp->conn_bound_addr_v6)) {
break;
}
continue;
}
if (connp->conn_ipversion != lconnp->conn_ipversion &&
bind_to_req_port_only)
continue;
if (quick_connect &&
(ltcp->tcp_state > TCPS_LISTEN) &&
((connp->conn_fport != lconnp->conn_fport) ||
!IN6_ARE_ADDR_EQUAL(&connp->conn_faddr_v6,
&lconnp->conn_faddr_v6)))
continue;
if (!reuseaddr) {
if (!V6_OR_V4_INADDR_ANY(*laddr) &&
!V6_OR_V4_INADDR_ANY(
lconnp->conn_bound_addr_v6) &&
!IN6_ARE_ADDR_EQUAL(laddr,
&lconnp->conn_bound_addr_v6))
continue;
if (ltcp->tcp_state >= TCPS_BOUND) {
break;
}
} else {
if (IN6_ARE_ADDR_EQUAL(laddr,
&lconnp->conn_bound_addr_v6) &&
(ltcp->tcp_state == TCPS_LISTEN ||
ltcp->tcp_state == TCPS_BOUND))
break;
}
}
if (ltcp != NULL) {
mutex_exit(&tbf->tf_lock);
} else {
tcp->tcp_state = TCPS_BOUND;
DTRACE_TCP6(state__change, void, NULL,
ip_xmit_attr_t *, connp->conn_ixa,
void, NULL, tcp_t *, tcp, void, NULL,
int32_t, TCPS_IDLE);
connp->conn_lport = htons(port);
ASSERT(&tcps->tcps_bind_fanout[TCP_BIND_HASH(
connp->conn_lport)] == tbf);
tcp_bind_hash_insert(tbf, tcp, 1);
mutex_exit(&tbf->tf_lock);
if (user_specified)
return (port);
if (!connp->conn_anon_priv_bind)
tcps->tcps_next_port_to_try = port + 1;
return (port);
}
if (connp->conn_anon_priv_bind) {
port = tcp_get_next_priv_port(tcp);
} else {
if (count == 0 && user_specified) {
port =
tcp_update_next_port(
tcps->tcps_next_port_to_try,
tcp, B_TRUE);
user_specified = B_FALSE;
} else {
port = tcp_update_next_port(port + 1, tcp,
B_FALSE);
}
}
if (port == 0)
break;
} while (++count < loopmax);
return (0);
}