/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * This is used to support the hidden __sin6_src_id in the sockaddr_in6 * structure which is there to ensure that applications (such as UDP apps) * which get an address from recvfrom and use that address in a sendto * or connect will by default use the same source address in the "response" * as the destination address in the "request" they received. * * This is built using some new functions (in IP - doing their own locking * so they can be called from the transports) to map between integer IDs * and in6_addr_t. * The use applies to sockaddr_in6 - whether or not mapped addresses are used. * * This file contains the functions used by both IP and the transports * to implement __sin6_src_id. * The routines do their own locking since they are called from * the transports (to map between a source id and an address) * and from IP proper when IP addresses are added and removed. * * The routines handle both IPv4 and IPv6 with the IPv4 addresses represented * as IPv4-mapped addresses. */ #include #include #include #include #include #include #include #define _SUN_TPI_VERSION 2 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Data structure to represent addresses */ struct srcid_map { struct srcid_map *sm_next; in6_addr_t sm_addr; /* Local address */ uint_t sm_srcid; /* source id */ uint_t sm_refcnt; /* > 1 ipif with same addr? */ zoneid_t sm_zoneid; /* zone id */ }; typedef struct srcid_map srcid_map_t; static uint_t srcid_nextid(void); static srcid_map_t **srcid_lookup_addr(const in6_addr_t *addr, zoneid_t zoneid); static srcid_map_t **srcid_lookup_id(uint_t id); /* * ID used to assign next free one. * Increases by one. Once it wraps we search for an unused ID. */ static uint_t ip_src_id = 1; static boolean_t srcid_wrapped = B_FALSE; static srcid_map_t *srcid_head; krwlock_t srcid_lock; /* * Insert/add a new address to the map. * Returns zero if ok; otherwise errno (e.g. for memory allocation failure). */ int ip_srcid_insert(const in6_addr_t *addr, zoneid_t zoneid) { srcid_map_t **smpp; #ifdef DEBUG char abuf[INET6_ADDRSTRLEN]; ip1dbg(("ip_srcid_insert(%s, %d)\n", inet_ntop(AF_INET6, addr, abuf, sizeof (abuf)), zoneid)); #endif rw_enter(&srcid_lock, RW_WRITER); smpp = srcid_lookup_addr(addr, zoneid); if (*smpp != NULL) { /* Already present - increment refcount */ (*smpp)->sm_refcnt++; ASSERT((*smpp)->sm_refcnt != 0); /* wraparound */ rw_exit(&srcid_lock); return (0); } /* Insert new */ *smpp = kmem_alloc(sizeof (srcid_map_t), KM_NOSLEEP); if (*smpp == NULL) { rw_exit(&srcid_lock); return (ENOMEM); } (*smpp)->sm_next = NULL; (*smpp)->sm_addr = *addr; (*smpp)->sm_srcid = srcid_nextid(); (*smpp)->sm_refcnt = 1; (*smpp)->sm_zoneid = zoneid; rw_exit(&srcid_lock); return (0); } /* * Remove an new address from the map. * Returns zero if ok; otherwise errno (e.g. for nonexistent address). */ int ip_srcid_remove(const in6_addr_t *addr, zoneid_t zoneid) { srcid_map_t **smpp; srcid_map_t *smp; #ifdef DEBUG char abuf[INET6_ADDRSTRLEN]; ip1dbg(("ip_srcid_remove(%s, %d)\n", inet_ntop(AF_INET6, addr, abuf, sizeof (abuf)), zoneid)); #endif rw_enter(&srcid_lock, RW_WRITER); smpp = srcid_lookup_addr(addr, zoneid); smp = *smpp; if (smp == NULL) { /* Not preset */ rw_exit(&srcid_lock); return (ENOENT); } /* Decrement refcount */ ASSERT(smp->sm_refcnt != 0); smp->sm_refcnt--; if (smp->sm_refcnt != 0) { rw_exit(&srcid_lock); return (0); } /* Remove entry */ *smpp = smp->sm_next; rw_exit(&srcid_lock); smp->sm_next = NULL; kmem_free(smp, sizeof (srcid_map_t)); return (0); } /* * Map from an address to a source id. * If the address is unknown return the unknown id (zero). */ uint_t ip_srcid_find_addr(const in6_addr_t *addr, zoneid_t zoneid) { srcid_map_t **smpp; srcid_map_t *smp; uint_t id; rw_enter(&srcid_lock, RW_READER); smpp = srcid_lookup_addr(addr, zoneid); smp = *smpp; if (smp == NULL) { char abuf[INET6_ADDRSTRLEN]; /* Not present - could be broadcast or multicast address */ ip1dbg(("ip_srcid_find_addr: unknown %s in zone %d\n", inet_ntop(AF_INET6, addr, abuf, sizeof (abuf)), zoneid)); id = 0; } else { ASSERT(smp->sm_refcnt != 0); id = smp->sm_srcid; } rw_exit(&srcid_lock); return (id); } /* * Map from a source id to an address. * If the id is unknown return the unspecified address. */ void ip_srcid_find_id(uint_t id, in6_addr_t *addr, zoneid_t zoneid) { srcid_map_t **smpp; srcid_map_t *smp; rw_enter(&srcid_lock, RW_READER); smpp = srcid_lookup_id(id); smp = *smpp; if (smp == NULL || smp->sm_zoneid != zoneid) { /* Not preset */ ip1dbg(("ip_srcid_find_id: unknown %u or in wrong zone\n", id)); *addr = ipv6_all_zeros; } else { ASSERT(smp->sm_refcnt != 0); *addr = smp->sm_addr; } rw_exit(&srcid_lock); } /* * ndd report function */ /*ARGSUSED*/ int ip_srcid_report(queue_t *q, mblk_t *mp, caddr_t arg, cred_t *ioc_cr) { srcid_map_t *smp; char abuf[INET6_ADDRSTRLEN]; zoneid_t zoneid; zoneid = Q_TO_CONN(q)->conn_zoneid; (void) mi_mpprintf(mp, "addr " "id zone refcnt"); rw_enter(&srcid_lock, RW_READER); for (smp = srcid_head; smp != NULL; smp = smp->sm_next) { if (zoneid != GLOBAL_ZONEID && zoneid != smp->sm_zoneid) continue; (void) mi_mpprintf(mp, "%46s %5u %5d %5u", inet_ntop(AF_INET6, &smp->sm_addr, abuf, sizeof (abuf)), smp->sm_srcid, smp->sm_zoneid, smp->sm_refcnt); } rw_exit(&srcid_lock); return (0); } /* Assign the next available ID */ static uint_t srcid_nextid(void) { uint_t id; srcid_map_t **smpp; ASSERT(rw_owner(&srcid_lock) == curthread); if (!srcid_wrapped) { id = ip_src_id++; if (ip_src_id == 0) srcid_wrapped = B_TRUE; return (id); } /* Once it wraps we search for an unused ID. */ for (id = 0; id < 0xffffffff; id++) { smpp = srcid_lookup_id(id); if (*smpp == NULL) return (id); } panic("srcid_nextid: No free identifiers!"); /* NOTREACHED */ } /* * Lookup based on address. * Always returns a non-null pointer. * If found then *ptr will be the found object. * Otherwise *ptr will be NULL and can be used to insert a new object. */ static srcid_map_t ** srcid_lookup_addr(const in6_addr_t *addr, zoneid_t zoneid) { srcid_map_t **smpp; ASSERT(RW_LOCK_HELD(&srcid_lock)); smpp = &srcid_head; while (*smpp != NULL) { if (IN6_ARE_ADDR_EQUAL(&(*smpp)->sm_addr, addr) && zoneid == (*smpp)->sm_zoneid) return (smpp); smpp = &(*smpp)->sm_next; } return (smpp); } /* * Lookup based on address. * Always returns a non-null pointer. * If found then *ptr will be the found object. * Otherwise *ptr will be NULL and can be used to insert a new object. */ static srcid_map_t ** srcid_lookup_id(uint_t id) { srcid_map_t **smpp; ASSERT(RW_LOCK_HELD(&srcid_lock)); smpp = &srcid_head; while (*smpp != NULL) { if ((*smpp)->sm_srcid == id) return (smpp); smpp = &(*smpp)->sm_next; } return (smpp); }