#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/socket.h>
#include <sys/random.h>
#include <sys/tsol/tndb.h>
#include <sys/tsol/tnet.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/sctp.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ip_ire.h>
#include <inet/ip_if.h>
#include <inet/ip_ndp.h>
#include <inet/mib2.h>
#include <inet/nd.h>
#include <inet/optcom.h>
#include <inet/sctp_ip.h>
#include <inet/ipclassifier.h>
#include "sctp_impl.h"
#include "sctp_addr.h"
#include "sctp_asconf.h"
static struct kmem_cache *sctp_kmem_faddr_cache;
static void sctp_init_faddr(sctp_t *, sctp_faddr_t *, in6_addr_t *, mblk_t *);
void
sctp_set_saddr(sctp_t *sctp, sctp_faddr_t *fp)
{
boolean_t v6 = !fp->sf_isv4;
boolean_t addr_set;
fp->sf_saddr = sctp_get_valid_addr(sctp, v6, &addr_set);
if (!addr_set)
fp->sf_state = SCTP_FADDRS_UNREACH;
}
void
sctp_get_dest(sctp_t *sctp, sctp_faddr_t *fp)
{
in6_addr_t laddr;
in6_addr_t nexthop;
sctp_saddr_ipif_t *sp;
int hdrlen;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
iulp_t uinfo;
uint_t pmtu;
int error;
uint32_t flags = IPDF_VERIFY_DST | IPDF_IPSEC |
IPDF_SELECT_SRC | IPDF_UNIQUE_DCE;
fp->sf_saddr = ipv6_all_zeros;
if (fp->sf_state == SCTP_FADDRS_UNREACH) {
fp->sf_state = SCTP_FADDRS_UNCONFIRMED;
}
if (!sctps->sctps_ignore_path_mtu)
fp->sf_ixa->ixa_flags |= IXAF_PMTU_DISCOVERY;
ip_attr_nexthop(&connp->conn_xmit_ipp, fp->sf_ixa, &fp->sf_faddr,
&nexthop);
laddr = fp->sf_saddr;
error = ip_attr_connect(connp, fp->sf_ixa, &laddr, &fp->sf_faddr,
&nexthop, connp->conn_fport, &laddr, &uinfo, flags);
if (error != 0) {
dprint(3, ("sctp_get_dest: no ire for %x:%x:%x:%x\n",
SCTP_PRINTADDR(fp->sf_faddr)));
sctp_set_saddr(sctp, fp);
if (fp->sf_state == SCTP_FADDRS_UNREACH)
return;
goto check_current;
}
ASSERT(fp->sf_ixa->ixa_ire != NULL);
ASSERT(!(fp->sf_ixa->ixa_ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE)));
if (!sctp->sctp_loopback)
sctp->sctp_loopback = uinfo.iulp_loopback;
if ((sp = sctp_saddr_lookup(sctp, &laddr, 0)) != NULL &&
!sp->saddr_ipif_dontsrc) {
if (sp->saddr_ipif_unconfirmed == 1)
sp->saddr_ipif_unconfirmed = 0;
fp->sf_saddr = laddr;
} else {
dprint(2, ("sctp_get_dest: src addr is not part of assoc "
"%x:%x:%x:%x\n", SCTP_PRINTADDR(laddr)));
sctp_set_saddr(sctp, fp);
if (fp->sf_state == SCTP_FADDRS_UNREACH) {
return;
}
}
if (fp->sf_srtt == -1 && uinfo.iulp_rtt != 0) {
fp->sf_srtt = MSEC_TO_TICK(uinfo.iulp_rtt);
fp->sf_rttvar = MSEC_TO_TICK(uinfo.iulp_rtt_sd);
fp->sf_rto = 3 * fp->sf_srtt;
if (fp->sf_rto < sctp->sctp_rto_min) {
fp->sf_rto = sctp->sctp_rto_min;
}
if (fp->sf_rto > sctp->sctp_rto_max) {
fp->sf_rto = sctp->sctp_rto_max;
}
SCTP_MAX_RTO(sctp, fp);
}
pmtu = uinfo.iulp_mtu;
if (fp->sf_isv4) {
hdrlen = sctp->sctp_hdr_len;
} else {
hdrlen = sctp->sctp_hdr6_len;
}
if ((fp->sf_pmss + hdrlen) != pmtu) {
fp->sf_pmss = (pmtu - hdrlen) & ~(SCTP_ALIGN - 1);
if (fp->sf_cwnd < (fp->sf_pmss * 2)) {
SET_CWND(fp, fp->sf_pmss,
sctps->sctps_slow_start_initial);
}
}
check_current:
if (fp == sctp->sctp_current)
sctp_set_faddr_current(sctp, fp);
}
void
sctp_update_dce(sctp_t *sctp)
{
sctp_faddr_t *fp;
sctp_stack_t *sctps = sctp->sctp_sctps;
iulp_t uinfo;
ip_stack_t *ipst = sctps->sctps_netstack->netstack_ip;
uint_t ifindex;
for (fp = sctp->sctp_faddrs; fp != NULL; fp = fp->sf_next) {
bzero(&uinfo, sizeof (uinfo));
if (fp->sf_pmtu_discovered) {
if (fp->sf_isv4) {
uinfo.iulp_mtu = fp->sf_pmss +
sctp->sctp_hdr_len;
} else {
uinfo.iulp_mtu = fp->sf_pmss +
sctp->sctp_hdr6_len;
}
}
if (sctps->sctps_rtt_updates != 0 &&
fp->sf_rtt_updates >= sctps->sctps_rtt_updates) {
uinfo.iulp_rtt = TICK_TO_MSEC(fp->sf_srtt);
uinfo.iulp_rtt_sd = TICK_TO_MSEC(fp->sf_rttvar);
fp->sf_rtt_updates = 0;
}
ifindex = 0;
if (IN6_IS_ADDR_LINKSCOPE(&fp->sf_faddr)) {
if (fp->sf_ixa->ixa_nce != NULL) {
ifindex = fp->sf_ixa->ixa_nce->nce_common->
ncec_ill->ill_phyint->phyint_ifindex;
} else {
continue;
}
}
(void) dce_update_uinfo(&fp->sf_faddr, ifindex, &uinfo, ipst);
}
}
mblk_t *
sctp_make_mp(sctp_t *sctp, sctp_faddr_t *fp, int trailer)
{
mblk_t *mp;
size_t ipsctplen;
int isv4;
sctp_stack_t *sctps = sctp->sctp_sctps;
boolean_t src_changed = B_FALSE;
ASSERT(fp != NULL);
isv4 = fp->sf_isv4;
if (SCTP_IS_ADDR_UNSPEC(isv4, fp->sf_saddr) ||
(fp->sf_ixa->ixa_ire->ire_flags & (RTF_REJECT|RTF_BLACKHOLE))) {
sctp_get_dest(sctp, fp);
src_changed = B_TRUE;
}
if (fp->sf_state == SCTP_FADDRS_UNREACH)
return (NULL);
ASSERT(fp->sf_ixa->ixa_ire != NULL);
ASSERT(!SCTP_IS_ADDR_UNSPEC(isv4, fp->sf_saddr));
if (isv4) {
ipsctplen = sctp->sctp_hdr_len;
} else {
ipsctplen = sctp->sctp_hdr6_len;
}
mp = allocb(ipsctplen + sctps->sctps_wroff_xtra + trailer, BPRI_MED);
if (mp == NULL) {
ip1dbg(("sctp_make_mp: error making mp..\n"));
return (NULL);
}
mp->b_rptr += sctps->sctps_wroff_xtra;
mp->b_wptr = mp->b_rptr + ipsctplen;
ASSERT(OK_32PTR(mp->b_wptr));
if (isv4) {
ipha_t *iph = (ipha_t *)mp->b_rptr;
bcopy(sctp->sctp_iphc, mp->b_rptr, ipsctplen);
if (fp != sctp->sctp_current || src_changed) {
IN6_V4MAPPED_TO_IPADDR(&fp->sf_faddr, iph->ipha_dst);
IN6_V4MAPPED_TO_IPADDR(&fp->sf_saddr, iph->ipha_src);
}
if (fp->sf_df) {
iph->ipha_fragment_offset_and_flags = htons(IPH_DF);
} else {
iph->ipha_fragment_offset_and_flags = 0;
}
} else {
bcopy(sctp->sctp_iphc6, mp->b_rptr, ipsctplen);
if (fp != sctp->sctp_current || src_changed) {
((ip6_t *)(mp->b_rptr))->ip6_dst = fp->sf_faddr;
((ip6_t *)(mp->b_rptr))->ip6_src = fp->sf_saddr;
}
}
ASSERT(sctp->sctp_connp != NULL);
return (mp);
}
void
sctp_set_ulp_prop(sctp_t *sctp)
{
int hdrlen;
struct sock_proto_props sopp;
sctp_stack_t *sctps = sctp->sctp_sctps;
if (sctp->sctp_current->sf_isv4) {
hdrlen = sctp->sctp_hdr_len;
} else {
hdrlen = sctp->sctp_hdr6_len;
}
ASSERT(sctp->sctp_ulpd);
sctp->sctp_connp->conn_wroff = sctps->sctps_wroff_xtra + hdrlen +
sizeof (sctp_data_hdr_t);
ASSERT(sctp->sctp_current->sf_pmss == sctp->sctp_mss);
bzero(&sopp, sizeof (sopp));
sopp.sopp_flags = SOCKOPT_MAXBLK|SOCKOPT_WROFF;
sopp.sopp_wroff = sctp->sctp_connp->conn_wroff;
sopp.sopp_maxblk = sctp->sctp_mss - sizeof (sctp_data_hdr_t);
sctp->sctp_ulp_prop(sctp->sctp_ulpd, &sopp);
}
void
sctp_set_iplen(sctp_t *sctp, mblk_t *mp, ip_xmit_attr_t *ixa)
{
uint16_t sum = 0;
ipha_t *iph;
ip6_t *ip6h;
mblk_t *pmp = mp;
boolean_t isv4;
isv4 = (IPH_HDR_VERSION(mp->b_rptr) == IPV4_VERSION);
for (; pmp; pmp = pmp->b_cont)
sum += pmp->b_wptr - pmp->b_rptr;
ixa->ixa_pktlen = sum;
if (isv4) {
iph = (ipha_t *)mp->b_rptr;
iph->ipha_length = htons(sum);
ixa->ixa_ip_hdr_length = sctp->sctp_ip_hdr_len;
} else {
ip6h = (ip6_t *)mp->b_rptr;
ip6h->ip6_plen = htons(sum - IPV6_HDR_LEN);
ixa->ixa_ip_hdr_length = sctp->sctp_ip_hdr6_len;
}
}
int
sctp_compare_faddrsets(sctp_faddr_t *a1, sctp_faddr_t *a2)
{
int na1 = 0;
int overlap = 0;
int equal = 1;
int onematch;
sctp_faddr_t *fp1, *fp2;
for (fp1 = a1; fp1; fp1 = fp1->sf_next) {
onematch = 0;
for (fp2 = a2; fp2; fp2 = fp2->sf_next) {
if (IN6_ARE_ADDR_EQUAL(&fp1->sf_faddr,
&fp2->sf_faddr)) {
overlap++;
onematch = 1;
break;
}
if (!onematch) {
equal = 0;
}
}
na1++;
}
if (equal) {
return (SCTP_ADDR_EQUAL);
}
if (overlap == na1) {
return (SCTP_ADDR_SUBSET);
}
if (overlap) {
return (SCTP_ADDR_OVERLAP);
}
return (SCTP_ADDR_DISJOINT);
}
int
sctp_add_faddr(sctp_t *sctp, in6_addr_t *addr, int sleep, boolean_t first)
{
sctp_faddr_t *faddr;
mblk_t *timer_mp;
int err;
conn_t *connp = sctp->sctp_connp;
if (is_system_labeled()) {
ip_xmit_attr_t *ixa = connp->conn_ixa;
ts_label_t *effective_tsl = NULL;
ASSERT(ixa->ixa_tsl != NULL);
if (IN6_IS_ADDR_V4MAPPED(addr)) {
uint32_t dst;
IN6_V4MAPPED_TO_IPADDR(addr, dst);
err = tsol_check_dest(ixa->ixa_tsl,
&dst, IPV4_VERSION, connp->conn_mac_mode,
connp->conn_zone_is_global, &effective_tsl);
} else {
err = tsol_check_dest(ixa->ixa_tsl,
addr, IPV6_VERSION, connp->conn_mac_mode,
connp->conn_zone_is_global, &effective_tsl);
}
if (err != 0)
return (err);
if (sctp->sctp_faddrs == NULL && effective_tsl != NULL) {
ip_xmit_attr_replace_tsl(ixa, effective_tsl);
} else if (effective_tsl != NULL) {
label_rele(effective_tsl);
return (EHOSTUNREACH);
}
}
if ((faddr = kmem_cache_alloc(sctp_kmem_faddr_cache, sleep)) == NULL)
return (ENOMEM);
bzero(faddr, sizeof (*faddr));
timer_mp = sctp_timer_alloc((sctp), sctp_rexmit_timer, sleep);
if (timer_mp == NULL) {
kmem_cache_free(sctp_kmem_faddr_cache, faddr);
return (ENOMEM);
}
((sctpt_t *)(timer_mp->b_rptr))->sctpt_faddr = faddr;
faddr->sf_ixa = conn_get_ixa_exclusive(connp);
if (faddr->sf_ixa == NULL) {
freemsg(timer_mp);
kmem_cache_free(sctp_kmem_faddr_cache, faddr);
return (ENOMEM);
}
faddr->sf_ixa->ixa_notify_cookie = connp->conn_sctp;
sctp_init_faddr(sctp, faddr, addr, timer_mp);
ASSERT(faddr->sf_ixa->ixa_cred != NULL);
ASSERT(faddr->sf_next == NULL);
if (sctp->sctp_faddrs == NULL) {
ASSERT(sctp->sctp_lastfaddr == NULL);
sctp->sctp_faddrs = sctp->sctp_lastfaddr = faddr;
} else if (first) {
ASSERT(sctp->sctp_lastfaddr != NULL);
faddr->sf_next = sctp->sctp_faddrs;
sctp->sctp_faddrs = faddr;
} else {
sctp->sctp_lastfaddr->sf_next = faddr;
sctp->sctp_lastfaddr = faddr;
}
sctp->sctp_nfaddrs++;
return (0);
}
sctp_faddr_t *
sctp_lookup_faddr(sctp_t *sctp, in6_addr_t *addr)
{
sctp_faddr_t *fp;
for (fp = sctp->sctp_faddrs; fp != NULL; fp = fp->sf_next) {
if (IN6_ARE_ADDR_EQUAL(&fp->sf_faddr, addr))
break;
}
return (fp);
}
sctp_faddr_t *
sctp_lookup_faddr_nosctp(sctp_faddr_t *fp, in6_addr_t *addr)
{
for (; fp; fp = fp->sf_next) {
if (IN6_ARE_ADDR_EQUAL(&fp->sf_faddr, addr)) {
break;
}
}
return (fp);
}
void
sctp_set_faddr_current(sctp_t *sctp, sctp_faddr_t *fp)
{
if (fp->sf_isv4) {
IN6_V4MAPPED_TO_IPADDR(&fp->sf_faddr,
sctp->sctp_ipha->ipha_dst);
IN6_V4MAPPED_TO_IPADDR(&fp->sf_saddr,
sctp->sctp_ipha->ipha_src);
if (fp->sf_df) {
sctp->sctp_ipha->ipha_fragment_offset_and_flags =
htons(IPH_DF);
} else {
sctp->sctp_ipha->ipha_fragment_offset_and_flags = 0;
}
} else {
sctp->sctp_ip6h->ip6_dst = fp->sf_faddr;
sctp->sctp_ip6h->ip6_src = fp->sf_saddr;
}
sctp->sctp_current = fp;
sctp->sctp_mss = fp->sf_pmss;
if (!SCTP_IS_DETACHED(sctp))
sctp_set_ulp_prop(sctp);
}
void
sctp_redo_faddr_srcs(sctp_t *sctp)
{
sctp_faddr_t *fp;
for (fp = sctp->sctp_faddrs; fp != NULL; fp = fp->sf_next) {
sctp_get_dest(sctp, fp);
}
}
void
sctp_faddr_alive(sctp_t *sctp, sctp_faddr_t *fp)
{
int64_t now = LBOLT_FASTPATH64;
if (!sctp->sctp_zero_win_probe || !sctp->sctp_sctps->sctps_reclaim) {
sctp->sctp_strikes = 0;
}
fp->sf_strikes = 0;
fp->sf_lastactive = now;
fp->sf_hb_expiry = now + SET_HB_INTVL(fp);
fp->sf_hb_pending = B_FALSE;
if (fp->sf_state != SCTP_FADDRS_ALIVE) {
fp->sf_state = SCTP_FADDRS_ALIVE;
sctp_intf_event(sctp, fp->sf_faddr, SCTP_ADDR_AVAILABLE, 0);
sctp_get_dest(sctp, fp);
if (fp == sctp->sctp_primary &&
fp->sf_state != SCTP_FADDRS_UNREACH) {
sctp_set_faddr_current(sctp, fp);
return;
}
}
}
boolean_t
sctp_is_a_faddr_clean(sctp_t *sctp)
{
sctp_faddr_t *fp;
for (fp = sctp->sctp_faddrs; fp; fp = fp->sf_next) {
if (fp->sf_state == SCTP_FADDRS_ALIVE && fp->sf_strikes == 0) {
return (B_TRUE);
}
}
return (B_FALSE);
}
int
sctp_faddr_dead(sctp_t *sctp, sctp_faddr_t *fp, int newstate)
{
sctp_faddr_t *ofp;
sctp_stack_t *sctps = sctp->sctp_sctps;
if (fp->sf_state == SCTP_FADDRS_ALIVE) {
sctp_intf_event(sctp, fp->sf_faddr, SCTP_ADDR_UNREACHABLE, 0);
}
fp->sf_state = newstate;
dprint(1, ("sctp_faddr_dead: %x:%x:%x:%x down (state=%d)\n",
SCTP_PRINTADDR(fp->sf_faddr), newstate));
if (fp == sctp->sctp_current) {
sctp->sctp_current = NULL;
}
ofp = fp;
for (fp = fp->sf_next; fp != NULL; fp = fp->sf_next) {
if (fp->sf_state == SCTP_FADDRS_ALIVE) {
break;
}
}
if (fp == NULL) {
for (fp = sctp->sctp_faddrs; fp != ofp; fp = fp->sf_next) {
if (fp->sf_state == SCTP_FADDRS_ALIVE) {
break;
}
}
}
if (fp != ofp) {
if (sctp->sctp_current == NULL) {
dprint(1, ("sctp_faddr_dead: failover->%x:%x:%x:%x\n",
SCTP_PRINTADDR(fp->sf_faddr)));
sctp_set_faddr_current(sctp, fp);
}
return (0);
}
dprint(1, ("sctp_faddr_dead: all faddrs down, killing assoc\n"));
SCTPS_BUMP_MIB(sctps, sctpAborted);
sctp_assoc_event(sctp, sctp->sctp_state < SCTPS_ESTABLISHED ?
SCTP_CANT_STR_ASSOC : SCTP_COMM_LOST, 0, NULL);
sctp_clean_death(sctp, sctp->sctp_client_errno ?
sctp->sctp_client_errno : ETIMEDOUT);
return (-1);
}
sctp_faddr_t *
sctp_rotate_faddr(sctp_t *sctp, sctp_faddr_t *ofp)
{
sctp_faddr_t *nfp = NULL;
sctp_faddr_t *saved_fp = NULL;
int min_strikes;
if (ofp == NULL) {
ofp = sctp->sctp_current;
}
if (sctp->sctp_nfaddrs < 2)
return (ofp);
min_strikes = ofp->sf_strikes;
nfp = ofp->sf_next;
while (nfp != ofp) {
if (nfp == NULL) {
nfp = sctp->sctp_faddrs;
continue;
}
if (nfp->sf_state == SCTP_FADDRS_ALIVE) {
if (nfp->sf_strikes == 0)
break;
if (nfp->sf_strikes < min_strikes) {
min_strikes = nfp->sf_strikes;
saved_fp = nfp;
}
}
nfp = nfp->sf_next;
}
if (nfp == ofp)
nfp = NULL;
if (nfp != NULL)
return (nfp);
if (saved_fp != NULL)
return (saved_fp);
return (ofp);
}
void
sctp_unlink_faddr(sctp_t *sctp, sctp_faddr_t *fp)
{
sctp_faddr_t *fpp;
fpp = NULL;
if (!sctp->sctp_faddrs) {
return;
}
if (fp->sf_timer_mp != NULL) {
sctp_timer_free(fp->sf_timer_mp);
fp->sf_timer_mp = NULL;
fp->sf_timer_running = 0;
}
if (fp->sf_rc_timer_mp != NULL) {
sctp_timer_free(fp->sf_rc_timer_mp);
fp->sf_rc_timer_mp = NULL;
fp->sf_rc_timer_running = 0;
}
if (fp->sf_ixa != NULL) {
ixa_refrele(fp->sf_ixa);
fp->sf_ixa = NULL;
}
if (fp == sctp->sctp_faddrs) {
goto gotit;
}
for (fpp = sctp->sctp_faddrs; fpp->sf_next != fp; fpp = fpp->sf_next)
;
gotit:
ASSERT(sctp->sctp_conn_tfp != NULL);
mutex_enter(&sctp->sctp_conn_tfp->tf_lock);
if (fp == sctp->sctp_faddrs) {
sctp->sctp_faddrs = fp->sf_next;
} else {
fpp->sf_next = fp->sf_next;
}
mutex_exit(&sctp->sctp_conn_tfp->tf_lock);
kmem_cache_free(sctp_kmem_faddr_cache, fp);
sctp->sctp_nfaddrs--;
}
void
sctp_zap_faddrs(sctp_t *sctp, int caller_holds_lock)
{
sctp_faddr_t *fp, *fpn;
if (sctp->sctp_faddrs == NULL) {
ASSERT(sctp->sctp_lastfaddr == NULL);
return;
}
ASSERT(sctp->sctp_lastfaddr != NULL);
sctp->sctp_lastfaddr = NULL;
sctp->sctp_current = NULL;
sctp->sctp_primary = NULL;
sctp_free_faddr_timers(sctp);
if (sctp->sctp_conn_tfp != NULL && !caller_holds_lock) {
mutex_enter(&sctp->sctp_conn_tfp->tf_lock);
}
for (fp = sctp->sctp_faddrs; fp; fp = fpn) {
fpn = fp->sf_next;
if (fp->sf_ixa != NULL) {
ixa_refrele(fp->sf_ixa);
fp->sf_ixa = NULL;
}
kmem_cache_free(sctp_kmem_faddr_cache, fp);
sctp->sctp_nfaddrs--;
}
sctp->sctp_faddrs = NULL;
ASSERT(sctp->sctp_nfaddrs == 0);
if (sctp->sctp_conn_tfp != NULL && !caller_holds_lock) {
mutex_exit(&sctp->sctp_conn_tfp->tf_lock);
}
}
void
sctp_zap_addrs(sctp_t *sctp)
{
sctp_zap_faddrs(sctp, 0);
sctp_free_saddrs(sctp);
}
int
sctp_build_hdrs(sctp_t *sctp, int sleep)
{
conn_t *connp = sctp->sctp_connp;
ip_pkt_t *ipp = &connp->conn_xmit_ipp;
uint_t ip_hdr_length;
uchar_t *hdrs;
uint_t hdrs_len;
uint_t ulp_hdr_length = sizeof (sctp_hdr_t);
ipha_t *ipha;
ip6_t *ip6h;
sctp_hdr_t *sctph;
in6_addr_t v6src, v6dst;
ipaddr_t v4src, v4dst;
v4src = connp->conn_saddr_v4;
v4dst = connp->conn_faddr_v4;
v6src = connp->conn_saddr_v6;
v6dst = connp->conn_faddr_v6;
ip_hdr_length = ip_total_hdrs_len_v4(ipp);
if (ip_hdr_length > IP_MAX_HDR_LENGTH) {
return (EHOSTUNREACH);
}
hdrs_len = ip_hdr_length + ulp_hdr_length;
ASSERT(hdrs_len != 0);
if (hdrs_len != sctp->sctp_iphc_len) {
hdrs = kmem_alloc(hdrs_len, sleep);
if (hdrs == NULL)
return (ENOMEM);
if (sctp->sctp_iphc != NULL)
kmem_free(sctp->sctp_iphc, sctp->sctp_iphc_len);
sctp->sctp_iphc = hdrs;
sctp->sctp_iphc_len = hdrs_len;
} else {
hdrs = sctp->sctp_iphc;
}
sctp->sctp_hdr_len = sctp->sctp_iphc_len;
sctp->sctp_ip_hdr_len = ip_hdr_length;
sctph = (sctp_hdr_t *)(hdrs + ip_hdr_length);
sctp->sctp_sctph = sctph;
sctph->sh_sport = connp->conn_lport;
sctph->sh_dport = connp->conn_fport;
sctph->sh_verf = sctp->sctp_fvtag;
sctph->sh_chksum = 0;
ipha = (ipha_t *)hdrs;
sctp->sctp_ipha = ipha;
ipha->ipha_src = v4src;
ipha->ipha_dst = v4dst;
ip_build_hdrs_v4(hdrs, ip_hdr_length, ipp, connp->conn_proto);
ipha->ipha_length = htons(hdrs_len);
ipha->ipha_fragment_offset_and_flags = 0;
if (ipp->ipp_fields & IPPF_IPV4_OPTIONS)
(void) ip_massage_options(ipha, connp->conn_netstack);
ip_hdr_length = ip_total_hdrs_len_v6(ipp);
hdrs_len = ip_hdr_length + ulp_hdr_length;
ASSERT(hdrs_len != 0);
if (hdrs_len != sctp->sctp_iphc6_len) {
hdrs = kmem_alloc(hdrs_len, sleep);
if (hdrs == NULL)
return (ENOMEM);
if (sctp->sctp_iphc6 != NULL)
kmem_free(sctp->sctp_iphc6, sctp->sctp_iphc6_len);
sctp->sctp_iphc6 = hdrs;
sctp->sctp_iphc6_len = hdrs_len;
} else {
hdrs = sctp->sctp_iphc6;
}
sctp->sctp_hdr6_len = sctp->sctp_iphc6_len;
sctp->sctp_ip_hdr6_len = ip_hdr_length;
sctph = (sctp_hdr_t *)(hdrs + ip_hdr_length);
sctp->sctp_sctph6 = sctph;
sctph->sh_sport = connp->conn_lport;
sctph->sh_dport = connp->conn_fport;
sctph->sh_verf = sctp->sctp_fvtag;
sctph->sh_chksum = 0;
ip6h = (ip6_t *)hdrs;
sctp->sctp_ip6h = ip6h;
ip6h->ip6_src = v6src;
ip6h->ip6_dst = v6dst;
ip_build_hdrs_v6(hdrs, ip_hdr_length, ipp, connp->conn_proto,
connp->conn_flowinfo);
ip6h->ip6_plen = htons(hdrs_len - IPV6_HDR_LEN);
if (ipp->ipp_fields & IPPF_RTHDR) {
uint8_t *end;
ip6_rthdr_t *rth;
end = (uint8_t *)ip6h + ip_hdr_length;
rth = ip_find_rthdr_v6(ip6h, end);
if (rth != NULL) {
(void) ip_massage_options_v6(ip6h, rth,
connp->conn_netstack);
}
if (IN6_IS_ADDR_V4MAPPED(&ip6h->ip6_dst))
return (EADDRNOTAVAIL);
}
return (0);
}
static int
sctp_v4_label(sctp_t *sctp, sctp_faddr_t *fp)
{
conn_t *connp = sctp->sctp_connp;
ASSERT(fp->sf_ixa->ixa_flags & IXAF_IS_IPV4);
return (conn_update_label(connp, fp->sf_ixa, &fp->sf_faddr,
&connp->conn_xmit_ipp));
}
static int
sctp_v6_label(sctp_t *sctp, sctp_faddr_t *fp)
{
conn_t *connp = sctp->sctp_connp;
ASSERT(!(fp->sf_ixa->ixa_flags & IXAF_IS_IPV4));
return (conn_update_label(connp, fp->sf_ixa, &fp->sf_faddr,
&connp->conn_xmit_ipp));
}
int
sctp_set_hdraddrs(sctp_t *sctp)
{
sctp_faddr_t *fp;
int gotv4 = 0;
int gotv6 = 0;
conn_t *connp = sctp->sctp_connp;
ASSERT(sctp->sctp_faddrs != NULL);
ASSERT(sctp->sctp_nsaddrs > 0);
connp->conn_faddr_v6 = sctp->sctp_primary->sf_faddr;
connp->conn_saddr_v6 = sctp->sctp_primary->sf_saddr;
connp->conn_laddr_v6 = connp->conn_saddr_v6;
if (IN6_IS_ADDR_V4MAPPED(&sctp->sctp_primary->sf_faddr)) {
if (!is_system_labeled() ||
sctp_v4_label(sctp, sctp->sctp_primary) == 0) {
gotv4 = 1;
if (connp->conn_family == AF_INET) {
goto done;
}
}
} else {
if (!is_system_labeled() ||
sctp_v6_label(sctp, sctp->sctp_primary) == 0) {
gotv6 = 1;
}
}
for (fp = sctp->sctp_faddrs; fp; fp = fp->sf_next) {
if (!gotv4 && IN6_IS_ADDR_V4MAPPED(&fp->sf_faddr)) {
if (!is_system_labeled() ||
sctp_v4_label(sctp, fp) == 0) {
gotv4 = 1;
if (connp->conn_family == AF_INET || gotv6) {
break;
}
}
} else if (!gotv6 && !IN6_IS_ADDR_V4MAPPED(&fp->sf_faddr)) {
if (!is_system_labeled() ||
sctp_v6_label(sctp, fp) == 0) {
gotv6 = 1;
if (gotv4)
break;
}
}
}
done:
if (!gotv4 && !gotv6)
return (EACCES);
return (0);
}
void
sctp_add_unrec_parm(sctp_parm_hdr_t *uph, mblk_t **errmp,
boolean_t got_errchunk)
{
mblk_t *mp;
sctp_parm_hdr_t *ph;
size_t len;
int pad;
sctp_chunk_hdr_t *ecp;
len = sizeof (*ph) + ntohs(uph->sph_len);
if ((pad = len % SCTP_ALIGN) != 0) {
pad = SCTP_ALIGN - pad;
len += pad;
}
mp = allocb(len, BPRI_MED);
if (mp == NULL) {
return;
}
ph = (sctp_parm_hdr_t *)(mp->b_rptr);
ph->sph_type = htons(PARM_UNRECOGNIZED);
ph->sph_len = htons(len - pad);
bcopy(uph, ph + 1, ntohs(uph->sph_len));
if (pad != 0)
bzero((mp->b_rptr + len - pad), pad);
mp->b_wptr = mp->b_rptr + len;
if (*errmp != NULL) {
if (got_errchunk) {
ecp = (sctp_chunk_hdr_t *)((*errmp)->b_rptr);
ecp->sch_len = htons(ntohs(ecp->sch_len) + len);
}
linkb(*errmp, mp);
} else {
*errmp = mp;
}
}
sctp_parm_hdr_t *
sctp_next_parm(sctp_parm_hdr_t *current, ssize_t *remaining)
{
int pad;
uint16_t len;
len = ntohs(current->sph_len);
*remaining -= len;
if (*remaining < sizeof (*current) || len < sizeof (*current)) {
return (NULL);
}
if ((pad = len & (SCTP_ALIGN - 1)) != 0) {
pad = SCTP_ALIGN - pad;
*remaining -= pad;
}
current = (sctp_parm_hdr_t *)((char *)current + len + pad);
return (current);
}
int
sctp_get_addrparams(sctp_t *sctp, sctp_t *psctp, mblk_t *pkt,
sctp_chunk_hdr_t *ich, uint_t *sctp_options)
{
sctp_init_chunk_t *init;
ipha_t *iph;
ip6_t *ip6h;
in6_addr_t hdrsaddr[1];
in6_addr_t hdrdaddr[1];
sctp_parm_hdr_t *ph;
ssize_t remaining;
int isv4;
int err;
sctp_faddr_t *fp;
int supp_af = 0;
boolean_t check_saddr = B_TRUE;
in6_addr_t curaddr;
sctp_stack_t *sctps = sctp->sctp_sctps;
conn_t *connp = sctp->sctp_connp;
if (sctp_options != NULL)
*sctp_options = 0;
isv4 = (IPH_HDR_VERSION(pkt->b_rptr) == IPV4_VERSION);
if (isv4) {
iph = (ipha_t *)pkt->b_rptr;
IN6_IPADDR_TO_V4MAPPED(iph->ipha_src, hdrsaddr);
IN6_IPADDR_TO_V4MAPPED(iph->ipha_dst, hdrdaddr);
supp_af |= PARM_SUPP_V4;
} else {
ip6h = (ip6_t *)pkt->b_rptr;
hdrsaddr[0] = ip6h->ip6_src;
hdrdaddr[0] = ip6h->ip6_dst;
supp_af |= PARM_SUPP_V6;
}
if (psctp != NULL && psctp->sctp_nsaddrs > 0) {
ASSERT(sctp->sctp_nsaddrs == 0);
err = sctp_dup_saddrs(psctp, sctp, KM_NOSLEEP);
if (err != 0)
return (err);
}
fp = sctp_lookup_faddr(sctp, hdrsaddr);
if (fp == NULL) {
err = sctp_add_faddr(sctp, hdrsaddr, KM_NOSLEEP, B_TRUE);
if (err != 0)
return (err);
fp = sctp->sctp_faddrs;
}
if (cl_sctp_assoc_change != NULL && psctp == NULL)
curaddr = sctp->sctp_current->sf_faddr;
sctp->sctp_primary = fp;
sctp->sctp_current = fp;
sctp->sctp_mss = fp->sf_pmss;
if (sctp->sctp_loopback || sctp->sctp_linklocal) {
if (sctp->sctp_nsaddrs != 0)
sctp_free_saddrs(sctp);
if ((err = sctp_saddr_add_addr(sctp, hdrdaddr, 0)) != 0)
return (err);
if (sctp->sctp_loopback)
return (0);
check_saddr = B_FALSE;
}
remaining = ntohs(ich->sch_len) - sizeof (*ich) -
sizeof (sctp_init_chunk_t);
if (remaining < sizeof (*ph)) {
if (check_saddr) {
sctp_check_saddr(sctp, supp_af, psctp == NULL ?
B_FALSE : B_TRUE, hdrdaddr);
}
ASSERT(sctp_saddr_lookup(sctp, hdrdaddr, 0) != NULL);
return (0);
}
init = (sctp_init_chunk_t *)(ich + 1);
ph = (sctp_parm_hdr_t *)(init + 1);
while (ph != NULL) {
if (ph->sph_type == htons(PARM_SUPP_ADDRS)) {
int plen;
uint16_t *p;
uint16_t addrtype;
ASSERT(psctp != NULL);
plen = ntohs(ph->sph_len);
p = (uint16_t *)(ph + 1);
while (plen > 0) {
addrtype = ntohs(*p);
switch (addrtype) {
case PARM_ADDR6:
supp_af |= PARM_SUPP_V6;
break;
case PARM_ADDR4:
supp_af |= PARM_SUPP_V4;
break;
default:
break;
}
p++;
plen -= sizeof (*p);
}
} else if (ph->sph_type == htons(PARM_ADDR4)) {
if (remaining >= PARM_ADDR4_LEN) {
in6_addr_t addr;
ipaddr_t ta;
supp_af |= PARM_SUPP_V4;
bcopy(ph + 1, &ta, sizeof (ta));
if (ta == 0 ||
ta == INADDR_BROADCAST ||
ta == htonl(INADDR_LOOPBACK) ||
CLASSD(ta) || connp->conn_ipv6_v6only) {
goto next;
}
IN6_INADDR_TO_V4MAPPED((struct in_addr *)
(ph + 1), &addr);
if (sctp_lookup_faddr(sctp, &addr) != NULL)
goto next;
err = sctp_add_faddr(sctp, &addr, KM_NOSLEEP,
B_FALSE);
if (err != 0)
goto next;
}
} else if (ph->sph_type == htons(PARM_ADDR6) &&
connp->conn_family == AF_INET6) {
if (remaining >= PARM_ADDR6_LEN) {
in6_addr_t *addr6;
supp_af |= PARM_SUPP_V6;
addr6 = (in6_addr_t *)(ph + 1);
if (IN6_IS_ADDR_LINKLOCAL(addr6) ||
IN6_IS_ADDR_MULTICAST(addr6) ||
IN6_IS_ADDR_LOOPBACK(addr6) ||
IN6_IS_ADDR_V4MAPPED(addr6)) {
goto next;
}
if (sctp_lookup_faddr(sctp, addr6) != NULL)
goto next;
err = sctp_add_faddr(sctp,
(in6_addr_t *)(ph + 1), KM_NOSLEEP,
B_FALSE);
if (err != 0)
goto next;
}
} else if (ph->sph_type == htons(PARM_FORWARD_TSN)) {
if (sctp_options != NULL)
*sctp_options |= SCTP_PRSCTP_OPTION;
}
next:
ph = sctp_next_parm(ph, &remaining);
}
if (check_saddr) {
sctp_check_saddr(sctp, supp_af, psctp == NULL ? B_FALSE :
B_TRUE, hdrdaddr);
}
ASSERT(sctp_saddr_lookup(sctp, hdrdaddr, 0) != NULL);
if (psctp == NULL && cl_sctp_assoc_change != NULL) {
uchar_t *alist;
size_t asize;
uchar_t *dlist;
size_t dsize;
asize = sizeof (in6_addr_t) * sctp->sctp_nfaddrs;
alist = kmem_alloc(asize, KM_NOSLEEP);
if (alist == NULL) {
SCTP_KSTAT(sctps, sctp_cl_assoc_change);
return (ENOMEM);
}
dsize = sizeof (in6_addr_t);
dlist = kmem_alloc(dsize, KM_NOSLEEP);
if (dlist == NULL) {
kmem_free(alist, asize);
SCTP_KSTAT(sctps, sctp_cl_assoc_change);
return (ENOMEM);
}
bcopy(&curaddr, dlist, sizeof (curaddr));
sctp_get_faddr_list(sctp, alist, asize);
(*cl_sctp_assoc_change)(connp->conn_family, alist, asize,
sctp->sctp_nfaddrs, dlist, dsize, 1, SCTP_CL_PADDR,
(cl_sctp_handle_t)sctp);
}
return (0);
}
int
sctp_secure_restart_check(mblk_t *pkt, sctp_chunk_hdr_t *ich, uint32_t ports,
int sleep, sctp_stack_t *sctps, ip_recv_attr_t *ira)
{
sctp_faddr_t *fp, *fphead = NULL;
sctp_parm_hdr_t *ph;
ssize_t remaining;
int isv4;
ipha_t *iph;
ip6_t *ip6h;
in6_addr_t hdraddr[1];
int retval = 0;
sctp_tf_t *tf;
sctp_t *sctp;
int compres;
sctp_init_chunk_t *init;
int nadded = 0;
isv4 = (IPH_HDR_VERSION(pkt->b_rptr) == IPV4_VERSION);
if (isv4) {
iph = (ipha_t *)pkt->b_rptr;
IN6_IPADDR_TO_V4MAPPED(iph->ipha_src, hdraddr);
} else {
ip6h = (ip6_t *)pkt->b_rptr;
hdraddr[0] = ip6h->ip6_src;
}
remaining = ntohs(ich->sch_len) - sizeof (*ich) -
sizeof (sctp_init_chunk_t);
if (remaining < sizeof (*ph)) {
return (1);
}
init = (sctp_init_chunk_t *)(ich + 1);
ph = (sctp_parm_hdr_t *)(init + 1);
while (ph != NULL) {
sctp_faddr_t *fpa = NULL;
if (ph->sph_type == htons(PARM_ADDR4)) {
if (remaining >= PARM_ADDR4_LEN) {
in6_addr_t addr;
IN6_INADDR_TO_V4MAPPED((struct in_addr *)
(ph + 1), &addr);
fpa = kmem_cache_alloc(sctp_kmem_faddr_cache,
sleep);
if (fpa == NULL) {
goto done;
}
bzero(fpa, sizeof (*fpa));
fpa->sf_faddr = addr;
fpa->sf_next = NULL;
}
} else if (ph->sph_type == htons(PARM_ADDR6)) {
if (remaining >= PARM_ADDR6_LEN) {
fpa = kmem_cache_alloc(sctp_kmem_faddr_cache,
sleep);
if (fpa == NULL) {
goto done;
}
bzero(fpa, sizeof (*fpa));
bcopy(ph + 1, &fpa->sf_faddr,
sizeof (fpa->sf_faddr));
fpa->sf_next = NULL;
}
}
if (fpa != NULL) {
if (fphead == NULL) {
fphead = fpa;
} else {
fpa->sf_next = fphead;
fphead = fpa;
}
}
ph = sctp_next_parm(ph, &remaining);
}
if (fphead == NULL) {
return (1);
}
fp = sctp_lookup_faddr_nosctp(fphead, hdraddr);
if (fp == NULL) {
fp = kmem_cache_alloc(sctp_kmem_faddr_cache, sleep);
if (fp == NULL) {
goto done;
}
bzero(fp, sizeof (*fp));
fp->sf_faddr = *hdraddr;
fp->sf_next = fphead;
fphead = fp;
}
tf = &(sctps->sctps_conn_fanout[SCTP_CONN_HASH(sctps, ports)]);
mutex_enter(&tf->tf_lock);
for (sctp = tf->tf_sctp; sctp; sctp = sctp->sctp_conn_hash_next) {
if (ports != sctp->sctp_connp->conn_ports) {
continue;
}
compres = sctp_compare_faddrsets(fphead, sctp->sctp_faddrs);
if (compres <= SCTP_ADDR_SUBSET) {
retval = 1;
mutex_exit(&tf->tf_lock);
goto done;
}
if (compres == SCTP_ADDR_OVERLAP) {
dprint(1,
("new assoc from %x:%x:%x:%x overlaps with %p\n",
SCTP_PRINTADDR(*hdraddr), (void *)sctp));
for (fp = fphead; fp; fp = fp->sf_next) {
if (sctp_lookup_faddr(sctp, &fp->sf_faddr)) {
fp->sf_rto = 0;
} else {
fp->sf_rto = 1;
nadded++;
}
}
mutex_exit(&tf->tf_lock);
goto done;
}
}
mutex_exit(&tf->tf_lock);
retval = 1;
done:
if (nadded > 0) {
void *dtail;
size_t dlen;
dtail = kmem_alloc(PARM_ADDR6_LEN * nadded, KM_NOSLEEP);
if (dtail == NULL) {
goto cleanup;
}
ph = dtail;
dlen = 0;
for (fp = fphead; fp; fp = fp->sf_next) {
if (fp->sf_rto == 0) {
continue;
}
if (IN6_IS_ADDR_V4MAPPED(&fp->sf_faddr)) {
ipaddr_t addr4;
ph->sph_type = htons(PARM_ADDR4);
ph->sph_len = htons(PARM_ADDR4_LEN);
IN6_V4MAPPED_TO_IPADDR(&fp->sf_faddr, addr4);
ph++;
bcopy(&addr4, ph, sizeof (addr4));
ph = (sctp_parm_hdr_t *)
((char *)ph + sizeof (addr4));
dlen += PARM_ADDR4_LEN;
} else {
ph->sph_type = htons(PARM_ADDR6);
ph->sph_len = htons(PARM_ADDR6_LEN);
ph++;
bcopy(&fp->sf_faddr, ph, sizeof (fp->sf_faddr));
ph = (sctp_parm_hdr_t *)
((char *)ph + sizeof (fp->sf_faddr));
dlen += PARM_ADDR6_LEN;
}
}
sctp_send_abort(sctp, sctp_init2vtag(ich),
SCTP_ERR_RESTART_NEW_ADDRS, dtail, dlen, pkt, 0, B_TRUE,
ira);
kmem_free(dtail, PARM_ADDR6_LEN * nadded);
}
cleanup:
if (fphead) {
sctp_faddr_t *fpn;
for (fp = fphead; fp; fp = fpn) {
fpn = fp->sf_next;
if (fp->sf_ixa != NULL) {
ixa_refrele(fp->sf_ixa);
fp->sf_ixa = NULL;
}
kmem_cache_free(sctp_kmem_faddr_cache, fp);
}
}
return (retval);
}
void
sctp_congest_reset(sctp_t *sctp)
{
sctp_faddr_t *fp;
sctp_stack_t *sctps = sctp->sctp_sctps;
mblk_t *mp;
for (fp = sctp->sctp_faddrs; fp != NULL; fp = fp->sf_next) {
fp->sf_ssthresh = sctps->sctps_initial_mtu;
SET_CWND(fp, fp->sf_pmss, sctps->sctps_slow_start_initial);
fp->sf_suna = 0;
fp->sf_pba = 0;
}
while ((mp = sctp->sctp_xmit_head) != NULL) {
sctp->sctp_xmit_head = mp->b_next;
mp->b_next = NULL;
if (sctp->sctp_xmit_head != NULL)
sctp->sctp_xmit_head->b_prev = NULL;
sctp_sendfail_event(sctp, mp, 0, B_TRUE);
}
sctp->sctp_xmit_head = NULL;
sctp->sctp_xmit_tail = NULL;
sctp->sctp_xmit_unacked = NULL;
sctp->sctp_unacked = 0;
if (sctp->sctp_cxmit_list != NULL)
sctp_asconf_free_cxmit(sctp, NULL);
sctp->sctp_cxmit_list = NULL;
sctp->sctp_cchunk_pend = 0;
sctp->sctp_rexmitting = B_FALSE;
sctp->sctp_rxt_nxttsn = 0;
sctp->sctp_rxt_maxtsn = 0;
sctp->sctp_zero_win_probe = B_FALSE;
}
static void
sctp_init_faddr(sctp_t *sctp, sctp_faddr_t *fp, in6_addr_t *addr,
mblk_t *timer_mp)
{
sctp_stack_t *sctps = sctp->sctp_sctps;
ASSERT(fp->sf_ixa != NULL);
bcopy(addr, &fp->sf_faddr, sizeof (*addr));
if (IN6_IS_ADDR_V4MAPPED(addr)) {
fp->sf_isv4 = 1;
fp->sf_pmss =
(sctps->sctps_initial_mtu - sctp->sctp_hdr_len) &
~(SCTP_ALIGN - 1);
fp->sf_ixa->ixa_flags |= IXAF_IS_IPV4;
} else {
fp->sf_isv4 = 0;
fp->sf_pmss =
(sctps->sctps_initial_mtu - sctp->sctp_hdr6_len) &
~(SCTP_ALIGN - 1);
fp->sf_ixa->ixa_flags &= ~IXAF_IS_IPV4;
}
fp->sf_cwnd = sctps->sctps_slow_start_initial * fp->sf_pmss;
fp->sf_rto = MIN(sctp->sctp_rto_initial, sctp->sctp_rto_max_init);
SCTP_MAX_RTO(sctp, fp);
fp->sf_srtt = -1;
fp->sf_rtt_updates = 0;
fp->sf_strikes = 0;
fp->sf_max_retr = sctp->sctp_pp_max_rxt;
fp->sf_state = SCTP_FADDRS_UNCONFIRMED;
fp->sf_hb_interval = sctp->sctp_hb_interval;
fp->sf_ssthresh = sctps->sctps_initial_ssthresh;
fp->sf_suna = 0;
fp->sf_pba = 0;
fp->sf_acked = 0;
fp->sf_lastactive = fp->sf_hb_expiry = ddi_get_lbolt64();
fp->sf_timer_mp = timer_mp;
fp->sf_hb_pending = B_FALSE;
fp->sf_hb_enabled = B_TRUE;
fp->sf_df = 1;
fp->sf_pmtu_discovered = 0;
fp->sf_next = NULL;
fp->sf_T3expire = 0;
(void) random_get_pseudo_bytes((uint8_t *)&fp->sf_hb_secret,
sizeof (fp->sf_hb_secret));
fp->sf_rxt_unacked = 0;
sctp_get_dest(sctp, fp);
}
static int
faddr_constructor(void *buf, void *arg, int flags)
{
sctp_faddr_t *fp = buf;
fp->sf_timer_mp = NULL;
fp->sf_timer_running = 0;
fp->sf_rc_timer_mp = NULL;
fp->sf_rc_timer_running = 0;
return (0);
}
static void
faddr_destructor(void *buf, void *arg)
{
sctp_faddr_t *fp = buf;
ASSERT(fp->sf_timer_mp == NULL);
ASSERT(fp->sf_timer_running == 0);
ASSERT(fp->sf_rc_timer_mp == NULL);
ASSERT(fp->sf_rc_timer_running == 0);
}
void
sctp_faddr_init(void)
{
sctp_kmem_faddr_cache = kmem_cache_create("sctp_faddr_cache",
sizeof (sctp_faddr_t), 0, faddr_constructor, faddr_destructor,
NULL, NULL, NULL, 0);
}
void
sctp_faddr_fini(void)
{
kmem_cache_destroy(sctp_kmem_faddr_cache);
}