#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ksynch.h>
#include <sys/kmem.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/sysmacros.h>
#include <sys/cmn_err.h>
#include <sys/strsun.h>
#include <sys/zone.h>
#include <netinet/in.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ip6_asp.h>
#include <inet/ip_ire.h>
#include <inet/ip_if.h>
#include <inet/ipclassifier.h>
#define IN6ADDR_MASK128_INIT \
{ 0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU }
#define IN6ADDR_MASK96_INIT { 0xffffffffU, 0xffffffffU, 0xffffffffU, 0 }
#define IN6ADDR_MASK32_INIT { 0xffffffffU, 0, 0, 0 }
#ifdef _BIG_ENDIAN
#define IN6ADDR_MASK16_INIT { 0xffff0000U, 0, 0, 0 }
#define IN6ADDR_MASK10_INIT { 0xffc00000U, 0, 0, 0 }
#define IN6ADDR_MASK7_INIT { 0xfe000000U, 0, 0, 0 }
#else
#define IN6ADDR_MASK16_INIT { 0x0000ffffU, 0, 0, 0 }
#define IN6ADDR_MASK10_INIT { 0x0000c0ffU, 0, 0, 0 }
#define IN6ADDR_MASK7_INIT { 0x000000feU, 0, 0, 0 }
#endif
static ip6_asp_t default_ip6_asp_table[] = {
{ IN6ADDR_LOOPBACK_INIT, IN6ADDR_MASK128_INIT,
"Loopback", 50 },
{ IN6ADDR_ANY_INIT, IN6ADDR_MASK96_INIT,
"IPv4_Compatible", 1 },
#ifdef _BIG_ENDIAN
{ { 0, 0, 0x0000ffffU, 0 }, IN6ADDR_MASK96_INIT,
"IPv4", 35 },
{ { 0x20010000U, 0, 0, 0 }, IN6ADDR_MASK32_INIT,
"Teredo", 5 },
{ { 0x20020000U, 0, 0, 0 }, IN6ADDR_MASK16_INIT,
"6to4", 30 },
{ { 0x3ffe0000U, 0, 0, 0 }, IN6ADDR_MASK16_INIT,
"6bone", 1 },
{ { 0xfec00000U, 0, 0, 0 }, IN6ADDR_MASK10_INIT,
"Site_Local", 1 },
{ { 0xfc000000U, 0, 0, 0 }, IN6ADDR_MASK7_INIT,
"ULA", 3 },
#else
{ { 0, 0, 0xffff0000U, 0 }, IN6ADDR_MASK96_INIT,
"IPv4", 35 },
{ { 0x00000120U, 0, 0, 0 }, IN6ADDR_MASK32_INIT,
"Teredo", 5 },
{ { 0x00000220U, 0, 0, 0 }, IN6ADDR_MASK16_INIT,
"6to4", 30 },
{ { 0x0000fe3fU, 0, 0, 0 }, IN6ADDR_MASK16_INIT,
"6bone", 1 },
{ { 0x0000c0feU, 0, 0, 0 }, IN6ADDR_MASK10_INIT,
"Site_Local", 1 },
{ { 0x000000fcU, 0, 0, 0 }, IN6ADDR_MASK7_INIT,
"ULA", 3 },
#endif
{ IN6ADDR_ANY_INIT, IN6ADDR_ANY_INIT,
"Default", 40 }
};
static void ip6_asp_copy(ip6_asp_t *, ip6_asp_t *, uint_t);
static void ip6_asp_check_for_updates(ip_stack_t *);
void
ip6_asp_init(ip_stack_t *ipst)
{
mutex_init(&ipst->ips_ip6_asp_lock, NULL, MUTEX_DEFAULT, NULL);
ipst->ips_ip6_asp_table = default_ip6_asp_table;
ipst->ips_ip6_asp_table_count =
sizeof (default_ip6_asp_table) / sizeof (ip6_asp_t);
}
void
ip6_asp_free(ip_stack_t *ipst)
{
if (ipst->ips_ip6_asp_table != default_ip6_asp_table) {
kmem_free(ipst->ips_ip6_asp_table,
ipst->ips_ip6_asp_table_count * sizeof (ip6_asp_t));
ipst->ips_ip6_asp_table = NULL;
}
mutex_destroy(&ipst->ips_ip6_asp_lock);
}
boolean_t
ip6_asp_can_lookup(ip_stack_t *ipst)
{
mutex_enter(&ipst->ips_ip6_asp_lock);
if (ipst->ips_ip6_asp_uip) {
mutex_exit(&ipst->ips_ip6_asp_lock);
return (B_FALSE);
}
IP6_ASP_TABLE_REFHOLD(ipst);
mutex_exit(&ipst->ips_ip6_asp_lock);
return (B_TRUE);
}
void
ip6_asp_pending_op(queue_t *q, mblk_t *mp, aspfunc_t func)
{
conn_t *connp = Q_TO_CONN(q);
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
ASSERT((mp->b_prev == NULL) && (mp->b_queue == NULL) &&
(mp->b_next == NULL));
mp->b_queue = (void *)q;
mp->b_prev = (void *)func;
mp->b_next = NULL;
mutex_enter(&ipst->ips_ip6_asp_lock);
if (ipst->ips_ip6_asp_pending_ops == NULL) {
ASSERT(ipst->ips_ip6_asp_pending_ops_tail == NULL);
ipst->ips_ip6_asp_pending_ops =
ipst->ips_ip6_asp_pending_ops_tail = mp;
} else {
ipst->ips_ip6_asp_pending_ops_tail->b_next = mp;
ipst->ips_ip6_asp_pending_ops_tail = mp;
}
mutex_exit(&ipst->ips_ip6_asp_lock);
}
static void
ip6_asp_complete_op(ip_stack_t *ipst)
{
mblk_t *mp;
queue_t *q;
aspfunc_t func;
mutex_enter(&ipst->ips_ip6_asp_lock);
while (ipst->ips_ip6_asp_pending_ops != NULL) {
mp = ipst->ips_ip6_asp_pending_ops;
ipst->ips_ip6_asp_pending_ops = mp->b_next;
mp->b_next = NULL;
if (ipst->ips_ip6_asp_pending_ops == NULL)
ipst->ips_ip6_asp_pending_ops_tail = NULL;
mutex_exit(&ipst->ips_ip6_asp_lock);
q = (queue_t *)mp->b_queue;
func = (aspfunc_t)mp->b_prev;
mp->b_prev = NULL;
mp->b_queue = NULL;
(*func)(NULL, q, mp, NULL);
mutex_enter(&ipst->ips_ip6_asp_lock);
}
mutex_exit(&ipst->ips_ip6_asp_lock);
}
void
ip6_asp_table_refrele(ip_stack_t *ipst)
{
IP6_ASP_TABLE_REFRELE(ipst);
}
char *
ip6_asp_lookup(const in6_addr_t *addr, uint32_t *precedence, ip_stack_t *ipst)
{
ip6_asp_t *aspp;
ip6_asp_t *match = NULL;
ip6_asp_t *default_policy;
aspp = ipst->ips_ip6_asp_table;
default_policy = aspp + ipst->ips_ip6_asp_table_count - 1;
while (match == NULL) {
if (aspp == default_policy) {
match = aspp;
} else {
if (V6_MASK_EQ(*addr, aspp->ip6_asp_mask,
aspp->ip6_asp_prefix))
match = aspp;
else
aspp++;
}
}
if (precedence != NULL)
*precedence = match->ip6_asp_precedence;
return (match->ip6_asp_label);
}
void
ip6_asp_check_for_updates(ip_stack_t *ipst)
{
ip6_asp_t *table;
size_t table_size;
mblk_t *data_mp, *mp;
struct iocblk *iocp;
mutex_enter(&ipst->ips_ip6_asp_lock);
if (ipst->ips_ip6_asp_pending_update == NULL ||
ipst->ips_ip6_asp_refcnt > 0) {
mutex_exit(&ipst->ips_ip6_asp_lock);
return;
}
mp = ipst->ips_ip6_asp_pending_update;
ipst->ips_ip6_asp_pending_update = NULL;
ASSERT(mp->b_prev != NULL);
ipst->ips_ip6_asp_uip = B_TRUE;
iocp = (struct iocblk *)mp->b_rptr;
data_mp = mp->b_cont;
if (data_mp == NULL) {
table = NULL;
table_size = iocp->ioc_count;
} else {
table = (ip6_asp_t *)data_mp->b_rptr;
table_size = iocp->ioc_count;
}
ip6_asp_replace(mp, table, table_size, B_TRUE, ipst,
iocp->ioc_flag & IOC_MODELS);
}
void
ip6_asp_replace(mblk_t *mp, ip6_asp_t *new_table, size_t new_size,
boolean_t locked, ip_stack_t *ipst, model_t datamodel)
{
int ret_val = 0;
ip6_asp_t *tmp_table;
uint_t count;
queue_t *q;
struct iocblk *iocp;
#if defined(_SYSCALL32_IMPL) && _LONG_LONG_ALIGNMENT_32 == 4
size_t ip6_asp_size = SIZEOF_STRUCT(ip6_asp, datamodel);
#else
const size_t ip6_asp_size = sizeof (ip6_asp_t);
#endif
if (new_size % ip6_asp_size != 0) {
ip1dbg(("ip6_asp_replace: invalid table size\n"));
ret_val = EINVAL;
if (locked)
goto unlock_end;
goto replace_end;
} else {
count = new_size / ip6_asp_size;
}
if (!locked)
mutex_enter(&ipst->ips_ip6_asp_lock);
if (!locked && ipst->ips_ip6_asp_refcnt > 0) {
if (ipst->ips_ip6_asp_pending_update == NULL) {
ipst->ips_ip6_asp_pending_update = mp;
} else {
ip1dbg(("ip6_asp_replace: discarding request\n"));
mutex_exit(&ipst->ips_ip6_asp_lock);
ret_val = EAGAIN;
goto replace_end;
}
mutex_exit(&ipst->ips_ip6_asp_lock);
return;
}
if (!locked)
ipst->ips_ip6_asp_uip = B_TRUE;
ASSERT(ipst->ips_ip6_asp_refcnt == 0);
if (new_table == NULL) {
if (ipst->ips_ip6_asp_table == default_ip6_asp_table)
goto unlock_end;
kmem_free(ipst->ips_ip6_asp_table,
ipst->ips_ip6_asp_table_count * sizeof (ip6_asp_t));
ipst->ips_ip6_asp_table = default_ip6_asp_table;
ipst->ips_ip6_asp_table_count =
sizeof (default_ip6_asp_table) / sizeof (ip6_asp_t);
goto unlock_end;
}
if (count == 0) {
ret_val = EINVAL;
ip1dbg(("ip6_asp_replace: empty table\n"));
goto unlock_end;
}
if ((tmp_table = kmem_alloc(count * sizeof (ip6_asp_t), KM_NOSLEEP)) ==
NULL) {
ret_val = ENOMEM;
goto unlock_end;
}
#if defined(_SYSCALL32_IMPL) && _LONG_LONG_ALIGNMENT_32 == 4
if (datamodel == IOC_ILP32) {
ip6_asp_t *dst;
ip6_asp32_t *src;
int i;
if ((dst = kmem_zalloc(count * sizeof (*dst),
KM_NOSLEEP)) == NULL) {
kmem_free(tmp_table, count * sizeof (ip6_asp_t));
ret_val = ENOMEM;
goto unlock_end;
}
src = (void *)new_table;
for (i = 0; i < count; i++)
bcopy(src + i, dst + i, sizeof (*src));
ip6_asp_copy(dst, tmp_table, count);
kmem_free(dst, count * sizeof (*dst));
} else
#endif
ip6_asp_copy(new_table, tmp_table, count);
if (!IN6_IS_ADDR_UNSPECIFIED(&tmp_table[count - 1].ip6_asp_prefix) ||
!IN6_IS_ADDR_UNSPECIFIED(&tmp_table[count - 1].ip6_asp_mask)) {
ret_val = EINVAL;
kmem_free(tmp_table, count * sizeof (ip6_asp_t));
ip1dbg(("ip6_asp_replace: bad table: no default entry\n"));
goto unlock_end;
}
if (ipst->ips_ip6_asp_table != default_ip6_asp_table) {
kmem_free(ipst->ips_ip6_asp_table,
ipst->ips_ip6_asp_table_count * sizeof (ip6_asp_t));
}
ipst->ips_ip6_asp_table = tmp_table;
ipst->ips_ip6_asp_table_count = count;
unlock_end:
ipst->ips_ip6_asp_uip = B_FALSE;
mutex_exit(&ipst->ips_ip6_asp_lock);
ip_update_source_selection(ipst);
replace_end:
q = (queue_t *)mp->b_prev;
mp->b_prev = NULL;
if (q == NULL) {
freemsg(mp);
goto check_binds;
}
iocp = (struct iocblk *)mp->b_rptr;
iocp->ioc_error = ret_val;
iocp->ioc_count = 0;
DB_TYPE(mp) = (iocp->ioc_error == 0) ? M_IOCACK : M_IOCNAK;
qreply(q, mp);
check_binds:
ip6_asp_complete_op(ipst);
}
static void
ip6_asp_copy(ip6_asp_t *src_table, ip6_asp_t *dst_table, uint_t count)
{
ip6_asp_t *src_ptr, *src_limit, *dst_ptr, *dst_limit, *dp;
dst_table[0] = src_table[0];
if (count == 1)
return;
src_limit = src_table + count;
dst_limit = dst_table + 1;
for (src_ptr = src_table + 1; src_ptr != src_limit;
src_ptr++, dst_limit++) {
for (dst_ptr = dst_table; dst_ptr < dst_limit; dst_ptr++) {
if (ip_mask_to_plen_v6(&src_ptr->ip6_asp_mask) >
ip_mask_to_plen_v6(&dst_ptr->ip6_asp_mask)) {
for (dp = dst_limit - 1; dp >= dst_ptr; dp--)
*(dp + 1) = *dp;
break;
}
}
*dst_ptr = *src_ptr;
}
}
int
ip6_asp_get(ip6_asp_t *dtable, size_t dtable_size, ip_stack_t *ipst)
{
uint_t dtable_count;
if (dtable != NULL) {
if (dtable_size < sizeof (ip6_asp_t))
return (-1);
dtable_count = dtable_size / sizeof (ip6_asp_t);
bcopy(ipst->ips_ip6_asp_table, dtable,
MIN(ipst->ips_ip6_asp_table_count, dtable_count) *
sizeof (ip6_asp_t));
}
return (ipst->ips_ip6_asp_table_count);
}
boolean_t
ip6_asp_labelcmp(const char *label1, const char *label2)
{
int64_t *llptr1, *llptr2;
if (label1 == label2)
return (B_TRUE);
llptr1 = (int64_t *)label1;
llptr2 = (int64_t *)label2;
if (llptr1[0] == llptr2[0] && llptr1[1] == llptr2[1])
return (B_TRUE);
return (B_FALSE);
}