/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 static uint_t srcid_nextid(ip_stack_t *); static srcid_map_t **srcid_lookup_addr(const in6_addr_t *addr, zoneid_t zoneid, ip_stack_t *); static srcid_map_t **srcid_lookup_id(uint_t id, ip_stack_t *); /* * 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, ip_stack_t *ipst) { 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(&ipst->ips_srcid_lock, RW_WRITER); smpp = srcid_lookup_addr(addr, zoneid, ipst); if (*smpp != NULL) { /* Already present - increment refcount */ (*smpp)->sm_refcnt++; ASSERT((*smpp)->sm_refcnt != 0); /* wraparound */ rw_exit(&ipst->ips_srcid_lock); return (0); } /* Insert new */ *smpp = kmem_alloc(sizeof (srcid_map_t), KM_NOSLEEP); if (*smpp == NULL) { rw_exit(&ipst->ips_srcid_lock); return (ENOMEM); } (*smpp)->sm_next = NULL; (*smpp)->sm_addr = *addr; (*smpp)->sm_srcid = srcid_nextid(ipst); (*smpp)->sm_refcnt = 1; (*smpp)->sm_zoneid = zoneid; rw_exit(&ipst->ips_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, ip_stack_t *ipst) { 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(&ipst->ips_srcid_lock, RW_WRITER); smpp = srcid_lookup_addr(addr, zoneid, ipst); smp = *smpp; if (smp == NULL) { /* Not preset */ rw_exit(&ipst->ips_srcid_lock); return (ENOENT); } /* Decrement refcount */ ASSERT(smp->sm_refcnt != 0); smp->sm_refcnt--; if (smp->sm_refcnt != 0) { rw_exit(&ipst->ips_srcid_lock); return (0); } /* Remove entry */ *smpp = smp->sm_next; rw_exit(&ipst->ips_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, netstack_t *ns) { srcid_map_t **smpp; srcid_map_t *smp; uint_t id; ip_stack_t *ipst = ns->netstack_ip; rw_enter(&ipst->ips_srcid_lock, RW_READER); smpp = srcid_lookup_addr(addr, zoneid, ipst); 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(&ipst->ips_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, netstack_t *ns) { srcid_map_t **smpp; srcid_map_t *smp; ip_stack_t *ipst = ns->netstack_ip; rw_enter(&ipst->ips_srcid_lock, RW_READER); smpp = srcid_lookup_id(id, ipst); smp = *smpp; if (smp == NULL || (smp->sm_zoneid != zoneid && zoneid != ALL_ZONES)) { /* 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(&ipst->ips_srcid_lock); } /* Assign the next available ID */ static uint_t srcid_nextid(ip_stack_t *ipst) { uint_t id; srcid_map_t **smpp; ASSERT(rw_owner(&ipst->ips_srcid_lock) == curthread); if (!ipst->ips_srcid_wrapped) { id = ipst->ips_ip_src_id++; if (ipst->ips_ip_src_id == 0) ipst->ips_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, ipst); 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, ip_stack_t *ipst) { srcid_map_t **smpp; ASSERT(RW_LOCK_HELD(&ipst->ips_srcid_lock)); smpp = &ipst->ips_srcid_head; while (*smpp != NULL) { if (IN6_ARE_ADDR_EQUAL(&(*smpp)->sm_addr, addr) && (zoneid == (*smpp)->sm_zoneid || zoneid == ALL_ZONES)) 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, ip_stack_t *ipst) { srcid_map_t **smpp; ASSERT(RW_LOCK_HELD(&ipst->ips_srcid_lock)); smpp = &ipst->ips_srcid_head; while (*smpp != NULL) { if ((*smpp)->sm_srcid == id) return (smpp); smpp = &(*smpp)->sm_next; } return (smpp); }