Skip to content
Snippets Groups Projects
socket.c 187 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* SCTP kernel implementation
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * (C) Copyright IBM Corp. 2001, 2004
     * Copyright (c) 1999-2000 Cisco, Inc.
     * Copyright (c) 1999-2001 Motorola, Inc.
     * Copyright (c) 2001-2003 Intel Corp.
     * Copyright (c) 2001-2002 Nokia, Inc.
     * Copyright (c) 2001 La Monte H.P. Yarroll
     *
    
     * This file is part of the SCTP kernel implementation
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * These functions interface with the sockets layer to implement the
     * SCTP Extensions for the Sockets API.
     *
     * Note that the descriptions from the specification are USER level
     * functions--this file is the functions which populate the struct proto
     * for SCTP which is the BOTTOM of the sockets interface.
     *
    
     * This SCTP implementation is free software;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * you can redistribute it and/or modify it under the terms of
     * the GNU General Public License as published by
     * the Free Software Foundation; either version 2, or (at your option)
     * any later version.
     *
    
     * This SCTP implementation is distributed in the hope that it
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * will be useful, but WITHOUT ANY WARRANTY; without even the implied
     *                 ************************
     * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     * See the GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with GNU CC; see the file COPYING.  If not, write to
     * the Free Software Foundation, 59 Temple Place - Suite 330,
     * Boston, MA 02111-1307, USA.
     *
     * Please send any bug reports or fixes you make to the
     * email address(es):
     *    lksctp developers <lksctp-developers@lists.sourceforge.net>
     *
     * Or submit a bug report through the following website:
     *    http://www.sf.net/projects/lksctp
     *
     * Written or modified by:
     *    La Monte H.P. Yarroll <piggy@acm.org>
     *    Narasimha Budihal     <narsi@refcode.org>
     *    Karl Knutson          <karl@athena.chicago.il.us>
     *    Jon Grimm             <jgrimm@us.ibm.com>
     *    Xingang Guo           <xingang.guo@intel.com>
     *    Daisy Chang           <daisyc@us.ibm.com>
     *    Sridhar Samudrala     <samudrala@us.ibm.com>
     *    Inaky Perez-Gonzalez  <inaky.gonzalez@intel.com>
     *    Ardelle Fan	    <ardelle.fan@intel.com>
     *    Ryan Layer	    <rmlayer@us.ibm.com>
     *    Anup Pemmaiah         <pemmaiah@cc.usu.edu>
     *    Kevin Gao             <kevin.gao@intel.com>
     *
     * Any bugs reported given to us we will try to fix... any fixes shared will
     * be incorporated into the next SCTP release.
     */
    
    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/wait.h>
    #include <linux/time.h>
    #include <linux/ip.h>
    
    #include <linux/capability.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/fcntl.h>
    #include <linux/poll.h>
    #include <linux/init.h>
    #include <linux/crypto.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #include <net/ip.h>
    #include <net/icmp.h>
    #include <net/route.h>
    #include <net/ipv6.h>
    #include <net/inet_common.h>
    
    #include <linux/socket.h> /* for sa_family_t */
    #include <net/sock.h>
    #include <net/sctp/sctp.h>
    #include <net/sctp/sm.h>
    
    /* WARNING:  Please do not remove the SCTP_STATIC attribute to
     * any of the functions below as they are used to export functions
     * used by a project regression testsuite.
     */
    
    /* Forward declarations for internal helper functions. */
    static int sctp_writeable(struct sock *sk);
    static void sctp_wfree(struct sk_buff *skb);
    static int sctp_wait_for_sndbuf(struct sctp_association *, long *timeo_p,
    				size_t msg_len);
    static int sctp_wait_for_packet(struct sock * sk, int *err, long *timeo_p);
    static int sctp_wait_for_connect(struct sctp_association *, long *timeo_p);
    static int sctp_wait_for_accept(struct sock *sk, long timeo);
    static void sctp_wait_for_close(struct sock *sk, long timeo);
    static struct sctp_af *sctp_sockaddr_af(struct sctp_sock *opt,
    					union sctp_addr *addr, int len);
    static int sctp_bindx_add(struct sock *, struct sockaddr *, int);
    static int sctp_bindx_rem(struct sock *, struct sockaddr *, int);
    static int sctp_send_asconf_add_ip(struct sock *, struct sockaddr *, int);
    static int sctp_send_asconf_del_ip(struct sock *, struct sockaddr *, int);
    static int sctp_send_asconf(struct sctp_association *asoc,
    			    struct sctp_chunk *chunk);
    static int sctp_do_bind(struct sock *, union sctp_addr *, int);
    static int sctp_autobind(struct sock *sk);
    static void sctp_sock_migrate(struct sock *, struct sock *,
    			      struct sctp_association *, sctp_socket_type_t);
    static char *sctp_hmac_alg = SCTP_COOKIE_HMAC_ALG;
    
    
    extern struct kmem_cache *sctp_bucket_cachep;
    extern int sysctl_sctp_mem[3];
    extern int sysctl_sctp_rmem[3];
    extern int sysctl_sctp_wmem[3];
    
    
    static int sctp_memory_pressure;
    static atomic_t sctp_memory_allocated;
    
    struct percpu_counter sctp_sockets_allocated;
    
    static void sctp_enter_memory_pressure(struct sock *sk)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* Get the sndbuf space available at the time on the association.  */
    static inline int sctp_wspace(struct sctp_association *asoc)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (asoc->ep->sndbuf_policy)
    		amt = asoc->sndbuf_used;
    	else
    
    		amt = sk_wmem_alloc_get(asoc->base.sk);
    
    
    	if (amt >= asoc->base.sk->sk_sndbuf) {
    		if (asoc->base.sk->sk_userlocks & SOCK_SNDBUF_LOCK)
    			amt = 0;
    		else {
    			amt = sk_stream_wspace(asoc->base.sk);
    			if (amt < 0)
    				amt = 0;
    		}
    
    		amt = asoc->base.sk->sk_sndbuf - amt;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return amt;
    }
    
    /* Increment the used sndbuf space count of the corresponding association by
     * the size of the outgoing data chunk.
     * Also, set the skb destructor for sndbuf accounting later.
     *
     * Since it is always 1-1 between chunk and skb, and also a new skb is always
     * allocated for chunk bundling in sctp_packet_transmit(), we can use the
     * destructor in the data chunk skb for the purpose of the sndbuf space
     * tracking.
     */
    static inline void sctp_set_owner_w(struct sctp_chunk *chunk)
    {
    	struct sctp_association *asoc = chunk->asoc;
    	struct sock *sk = asoc->base.sk;
    
    	/* The sndbuf space is tracked per association.  */
    	sctp_association_hold(asoc);
    
    
    	skb_set_owner_w(chunk->skb, sk);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	chunk->skb->destructor = sctp_wfree;
    	/* Save the chunk pointer in skb for sctp_wfree to use later.  */
    	*((struct sctp_chunk **)(chunk->skb->cb)) = chunk;
    
    
    	asoc->sndbuf_used += SCTP_DATA_SNDSIZE(chunk) +
    				sizeof(struct sk_buff) +
    				sizeof(struct sctp_chunk);
    
    	atomic_add(sizeof(struct sctp_chunk), &sk->sk_wmem_alloc);
    
    	sk->sk_wmem_queued += chunk->skb->truesize;
    	sk_mem_charge(sk, chunk->skb->truesize);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /* Verify that this is a valid address. */
    static inline int sctp_verify_addr(struct sock *sk, union sctp_addr *addr,
    				   int len)
    {
    	struct sctp_af *af;
    
    	/* Verify basic sockaddr. */
    	af = sctp_sockaddr_af(sctp_sk(sk), addr, len);
    	if (!af)
    		return -EINVAL;
    
    	/* Is this a valid SCTP address?  */
    
    	if (!af->addr_valid(addr, sctp_sk(sk), NULL))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	if (!sctp_sk(sk)->pf->send_verify(sctp_sk(sk), (addr)))
    		return -EINVAL;
    
    	return 0;
    }
    
    /* Look up the association by its id.  If this is not a UDP-style
     * socket, the ID field is always ignored.
     */
    struct sctp_association *sctp_id2assoc(struct sock *sk, sctp_assoc_t id)
    {
    	struct sctp_association *asoc = NULL;
    
    	/* If this is not a UDP-style socket, assoc id should be ignored. */
    	if (!sctp_style(sk, UDP)) {
    		/* Return NULL if the socket state is not ESTABLISHED. It
    		 * could be a TCP-style listening socket or a socket which
    		 * hasn't yet called connect() to establish an association.
    		 */
    		if (!sctp_sstate(sk, ESTABLISHED))
    			return NULL;
    
    		/* Get the first and the only association from the list. */
    		if (!list_empty(&sctp_sk(sk)->ep->asocs))
    			asoc = list_entry(sctp_sk(sk)->ep->asocs.next,
    					  struct sctp_association, asocs);
    		return asoc;
    	}
    
    	/* Otherwise this is a UDP-style socket. */
    	if (!id || (id == (sctp_assoc_t)-1))
    		return NULL;
    
    	spin_lock_bh(&sctp_assocs_id_lock);
    	asoc = (struct sctp_association *)idr_find(&sctp_assocs_id, (int)id);
    	spin_unlock_bh(&sctp_assocs_id_lock);
    
    	if (!asoc || (asoc->base.sk != sk) || asoc->base.dead)
    		return NULL;
    
    	return asoc;
    }
    
    /* Look up the transport from an address and an assoc id. If both address and
     * id are specified, the associations matching the address and the id should be
     * the same.
     */
    static struct sctp_transport *sctp_addr_id2transport(struct sock *sk,
    					      struct sockaddr_storage *addr,
    					      sctp_assoc_t id)
    {
    	struct sctp_association *addr_asoc = NULL, *id_asoc = NULL;
    	struct sctp_transport *transport;
    	union sctp_addr *laddr = (union sctp_addr *)addr;
    
    	addr_asoc = sctp_endpoint_lookup_assoc(sctp_sk(sk)->ep,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					       &transport);
    
    	if (!addr_asoc)
    		return NULL;
    
    	id_asoc = sctp_id2assoc(sk, id);
    	if (id_asoc && (id_asoc != addr_asoc))
    		return NULL;
    
    	sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
    						(union sctp_addr *)addr);
    
    	return transport;
    }
    
    /* API 3.1.2 bind() - UDP Style Syntax
     * The syntax of bind() is,
     *
     *   ret = bind(int sd, struct sockaddr *addr, int addrlen);
     *
     *   sd      - the socket descriptor returned by socket().
     *   addr    - the address structure (struct sockaddr_in or struct
     *             sockaddr_in6 [RFC 2553]),
     *   addr_len - the size of the address structure.
     */
    
    SCTP_STATIC int sctp_bind(struct sock *sk, struct sockaddr *addr, int addr_len)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int retval = 0;
    
    	sctp_lock_sock(sk);
    
    
    	SCTP_DEBUG_PRINTK("sctp_bind(sk: %p, addr: %p, addr_len: %d)\n",
    			  sk, addr, addr_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Disallow binding twice. */
    	if (!sctp_sk(sk)->ep->base.bind_addr.port)
    
    		retval = sctp_do_bind(sk, (union sctp_addr *)addr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				      addr_len);
    	else
    		retval = -EINVAL;
    
    	sctp_release_sock(sk);
    
    	return retval;
    }
    
    static long sctp_get_port_local(struct sock *, union sctp_addr *);
    
    /* Verify this is a valid sockaddr. */
    static struct sctp_af *sctp_sockaddr_af(struct sctp_sock *opt,
    					union sctp_addr *addr, int len)
    {
    	struct sctp_af *af;
    
    	/* Check minimum size.  */
    	if (len < sizeof (struct sockaddr))
    		return NULL;
    
    
    	/* V4 mapped address are really of AF_INET family */
    	if (addr->sa.sa_family == AF_INET6 &&
    	    ipv6_addr_v4mapped(&addr->v6.sin6_addr)) {
    		if (!opt->pf->af_supported(AF_INET, opt))
    			return NULL;
    	} else {
    		/* Does this PF support this AF? */
    		if (!opt->pf->af_supported(addr->sa.sa_family, opt))
    			return NULL;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* If we get this far, af is valid. */
    	af = sctp_get_af_specific(addr->sa.sa_family);
    
    	if (len < af->sockaddr_len)
    		return NULL;
    
    	return af;
    }
    
    /* Bind a local address either to an endpoint or to an association.  */
    SCTP_STATIC int sctp_do_bind(struct sock *sk, union sctp_addr *addr, int len)
    {
    	struct sctp_sock *sp = sctp_sk(sk);
    	struct sctp_endpoint *ep = sp->ep;
    	struct sctp_bind_addr *bp = &ep->base.bind_addr;
    	struct sctp_af *af;
    	unsigned short snum;
    	int ret = 0;
    
    	/* Common sockaddr verification. */
    	af = sctp_sockaddr_af(sp, addr, len);
    
    	if (!af) {
    		SCTP_DEBUG_PRINTK("sctp_do_bind(sk: %p, newaddr: %p, len: %d) EINVAL\n",
    				  sk, addr, len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	}
    
    	snum = ntohs(addr->v4.sin_port);
    
    	SCTP_DEBUG_PRINTK_IPADDR("sctp_do_bind(sk: %p, new addr: ",
    				 ", port: %d, new port: %d, len: %d)\n",
    				 sk,
    				 addr,
    				 bp->port, snum,
    				 len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* PF specific bind() address verification. */
    	if (!sp->pf->bind_verify(sp, addr))
    		return -EADDRNOTAVAIL;
    
    
    	/* We must either be unbound, or bind to the same port.
    	 * It's OK to allow 0 ports if we are already bound.
    	 * We'll just inhert an already bound port in this case
    	 */
    	if (bp->port) {
    		if (!snum)
    			snum = bp->port;
    		else if (snum != bp->port) {
    			SCTP_DEBUG_PRINTK("sctp_do_bind:"
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				  " New port %d does not match existing port "
    				  "%d.\n", snum, bp->port);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
    		return -EACCES;
    
    
    	/* See if the address matches any of the addresses we may have
    	 * already bound before checking against other endpoints.
    	 */
    	if (sctp_bind_addr_match(bp, addr, sp))
    		return -EINVAL;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Make sure we are allowed to bind here.
    	 * The function sctp_get_port_local() does duplicate address
    	 * detection.
    	 */
    
    	addr->v4.sin_port = htons(snum);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if ((ret = sctp_get_port_local(sk, addr))) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	/* Refresh ephemeral port.  */
    	if (!bp->port)
    
    		bp->port = inet_sk(sk)->inet_num;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Add the address to the bind address list.
    	 * Use GFP_ATOMIC since BHs will be disabled.
    	 */
    
    	ret = sctp_add_bind_addr(bp, addr, SCTP_ADDR_SRC, GFP_ATOMIC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Copy back into socket for getsockname() use. */
    	if (!ret) {
    
    		inet_sk(sk)->inet_sport = htons(inet_sk(sk)->inet_num);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		af->to_sk_saddr(addr, sk);
    	}
    
    	return ret;
    }
    
     /* ADDIP Section 4.1.1 Congestion Control of ASCONF Chunks
     *
    
     * R1) One and only one ASCONF Chunk MAY be in transit and unacknowledged
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * at any one time.  If a sender, after sending an ASCONF chunk, decides
    
     * it needs to transfer another ASCONF Chunk, it MUST wait until the
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * ASCONF-ACK Chunk returns from the previous ASCONF Chunk before sending a
    
     * subsequent ASCONF. Note this restriction binds each side, so at any
     * time two ASCONF may be in-transit on any given association (one sent
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * from each endpoint).
     */
    static int sctp_send_asconf(struct sctp_association *asoc,
    			    struct sctp_chunk *chunk)
    {
    	int		retval = 0;
    
    	/* If there is an outstanding ASCONF chunk, queue it for later
    	 * transmission.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (asoc->addip_last_asconf) {
    
    		list_add_tail(&chunk->list, &asoc->addip_chunk_list);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	/* Hold the chunk until an ASCONF_ACK is received. */
    	sctp_chunk_hold(chunk);
    	retval = sctp_primitive_ASCONF(asoc, chunk);
    	if (retval)
    		sctp_chunk_free(chunk);
    	else
    		asoc->addip_last_asconf = chunk;
    
    out:
    	return retval;
    }
    
    /* Add a list of addresses as bind addresses to local endpoint or
     * association.
     *
     * Basically run through each address specified in the addrs/addrcnt
     * array/length pair, determine if it is IPv6 or IPv4 and call
     * sctp_do_bind() on it.
     *
     * If any of them fails, then the operation will be reversed and the
     * ones that were added will be removed.
     *
     * Only sctp_setsockopt_bindx() is supposed to call this function.
     */
    
    static int sctp_bindx_add(struct sock *sk, struct sockaddr *addrs, int addrcnt)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int cnt;
    	int retval = 0;
    	void *addr_buf;
    	struct sockaddr *sa_addr;
    	struct sctp_af *af;
    
    	SCTP_DEBUG_PRINTK("sctp_bindx_add (sk: %p, addrs: %p, addrcnt: %d)\n",
    			  sk, addrs, addrcnt);
    
    	addr_buf = addrs;
    	for (cnt = 0; cnt < addrcnt; cnt++) {
    		/* The list may contain either IPv4 or IPv6 address;
    		 * determine the address length for walking thru the list.
    		 */
    		sa_addr = (struct sockaddr *)addr_buf;
    		af = sctp_get_af_specific(sa_addr->sa_family);
    		if (!af) {
    			retval = -EINVAL;
    			goto err_bindx_add;
    		}
    
    
    		retval = sctp_do_bind(sk, (union sctp_addr *)sa_addr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				      af->sockaddr_len);
    
    		addr_buf += af->sockaddr_len;
    
    err_bindx_add:
    		if (retval < 0) {
    			/* Failed. Cleanup the ones that have been added */
    			if (cnt > 0)
    				sctp_bindx_rem(sk, addrs, cnt);
    			return retval;
    		}
    	}
    
    	return retval;
    }
    
    /* Send an ASCONF chunk with Add IP address parameters to all the peers of the
     * associations that are part of the endpoint indicating that a list of local
     * addresses are added to the endpoint.
     *
    
     * If any of the addresses is already in the bind address list of the
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * association, we do not send the chunk for that association.  But it will not
     * affect other associations.
     *
     * Only sctp_setsockopt_bindx() is supposed to call this function.
     */
    
    static int sctp_send_asconf_add_ip(struct sock		*sk,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				   struct sockaddr	*addrs,
    				   int 			addrcnt)
    {
    	struct sctp_sock		*sp;
    	struct sctp_endpoint		*ep;
    	struct sctp_association		*asoc;
    	struct sctp_bind_addr		*bp;
    	struct sctp_chunk		*chunk;
    	struct sctp_sockaddr_entry	*laddr;
    	union sctp_addr			*addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	void				*addr_buf;
    	struct sctp_af			*af;
    	struct list_head		*p;
    	int 				i;
    	int 				retval = 0;
    
    	if (!sctp_addip_enable)
    		return retval;
    
    	sp = sctp_sk(sk);
    	ep = sp->ep;
    
    	SCTP_DEBUG_PRINTK("%s: (sk: %p, addrs: %p, addrcnt: %d)\n",
    
    			  __func__, sk, addrs, addrcnt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	list_for_each_entry(asoc, &ep->asocs, asocs) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (!asoc->peer.asconf_capable)
    			continue;
    
    		if (asoc->peer.addip_disabled_mask & SCTP_PARAM_ADD_IP)
    			continue;
    
    		if (!sctp_state(asoc, ESTABLISHED))
    			continue;
    
    		/* Check if any address in the packed array of addresses is
    
    		 * in the bind address list of the association. If so,
    		 * do not send the asconf chunk to its peer, but continue with
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 * other associations.
    		 */
    		addr_buf = addrs;
    		for (i = 0; i < addrcnt; i++) {
    			addr = (union sctp_addr *)addr_buf;
    			af = sctp_get_af_specific(addr->v4.sin_family);
    			if (!af) {
    				retval = -EINVAL;
    				goto out;
    			}
    
    			if (sctp_assoc_lookup_laddr(asoc, addr))
    				break;
    
    			addr_buf += af->sockaddr_len;
    		}
    		if (i < addrcnt)
    			continue;
    
    
    		/* Use the first valid address in bind addr list of
    		 * association as Address Parameter of ASCONF CHUNK.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 */
    		bp = &asoc->base.bind_addr;
    		p = bp->address_list.next;
    		laddr = list_entry(p, struct sctp_sockaddr_entry, list);
    
    		chunk = sctp_make_asconf_update_ip(asoc, &laddr->a, addrs,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    						   addrcnt, SCTP_PARAM_ADD_IP);
    		if (!chunk) {
    			retval = -ENOMEM;
    			goto out;
    		}
    
    		retval = sctp_send_asconf(asoc, chunk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		/* Add the new addresses to the bind address list with
    		 * use_as_src set to 0.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 */
    
    		addr_buf = addrs;
    		for (i = 0; i < addrcnt; i++) {
    			addr = (union sctp_addr *)addr_buf;
    			af = sctp_get_af_specific(addr->v4.sin_family);
    			memcpy(&saveaddr, addr, af->sockaddr_len);
    
    			retval = sctp_add_bind_addr(bp, &saveaddr,
    						    SCTP_ADDR_NEW, GFP_ATOMIC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    out:
    	return retval;
    }
    
    /* Remove a list of addresses from bind addresses list.  Do not remove the
     * last address.
     *
     * Basically run through each address specified in the addrs/addrcnt
     * array/length pair, determine if it is IPv6 or IPv4 and call
     * sctp_del_bind() on it.
     *
     * If any of them fails, then the operation will be reversed and the
     * ones that were removed will be added back.
     *
     * At least one address has to be left; if only one address is
     * available, the operation will return -EBUSY.
     *
     * Only sctp_setsockopt_bindx() is supposed to call this function.
     */
    
    static int sctp_bindx_rem(struct sock *sk, struct sockaddr *addrs, int addrcnt)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct sctp_sock *sp = sctp_sk(sk);
    	struct sctp_endpoint *ep = sp->ep;
    	int cnt;
    	struct sctp_bind_addr *bp = &ep->base.bind_addr;
    	int retval = 0;
    	void *addr_buf;
    
    	union sctp_addr *sa_addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_af *af;
    
    	SCTP_DEBUG_PRINTK("sctp_bindx_rem (sk: %p, addrs: %p, addrcnt: %d)\n",
    			  sk, addrs, addrcnt);
    
    	addr_buf = addrs;
    	for (cnt = 0; cnt < addrcnt; cnt++) {
    		/* If the bind address list is empty or if there is only one
    		 * bind address, there is nothing more to be removed (we need
    		 * at least one address here).
    		 */
    		if (list_empty(&bp->address_list) ||
    		    (sctp_list_single_entry(&bp->address_list))) {
    			retval = -EBUSY;
    			goto err_bindx_rem;
    		}
    
    
    		sa_addr = (union sctp_addr *)addr_buf;
    		af = sctp_get_af_specific(sa_addr->sa.sa_family);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!af) {
    			retval = -EINVAL;
    			goto err_bindx_rem;
    		}
    
    
    		if (!af->addr_valid(sa_addr, sp, NULL)) {
    			retval = -EADDRNOTAVAIL;
    			goto err_bindx_rem;
    		}
    
    
    		if (sa_addr->v4.sin_port != htons(bp->port)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			retval = -EINVAL;
    			goto err_bindx_rem;
    		}
    
    		/* FIXME - There is probably a need to check if sk->sk_saddr and
    		 * sk->sk_rcv_addr are currently set to one of the addresses to
    		 * be removed. This is something which needs to be looked into
    		 * when we are fixing the outstanding issues with multi-homing
    		 * socket routing and failover schemes. Refer to comments in
    		 * sctp_do_bind(). -daisy
    		 */
    
    		retval = sctp_del_bind_addr(bp, sa_addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		addr_buf += af->sockaddr_len;
    err_bindx_rem:
    		if (retval < 0) {
    			/* Failed. Add the ones that has been removed back */
    			if (cnt > 0)
    				sctp_bindx_add(sk, addrs, cnt);
    			return retval;
    		}
    	}
    
    	return retval;
    }
    
    /* Send an ASCONF chunk with Delete IP address parameters to all the peers of
     * the associations that are part of the endpoint indicating that a list of
     * local addresses are removed from the endpoint.
     *
    
     * If any of the addresses is already in the bind address list of the
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * association, we do not send the chunk for that association.  But it will not
     * affect other associations.
     *
     * Only sctp_setsockopt_bindx() is supposed to call this function.
     */
    static int sctp_send_asconf_del_ip(struct sock		*sk,
    				   struct sockaddr	*addrs,
    				   int			addrcnt)
    {
    	struct sctp_sock	*sp;
    	struct sctp_endpoint	*ep;
    	struct sctp_association	*asoc;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_bind_addr	*bp;
    	struct sctp_chunk	*chunk;
    	union sctp_addr		*laddr;
    	void			*addr_buf;
    	struct sctp_af		*af;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int 			i;
    	int 			retval = 0;
    
    	if (!sctp_addip_enable)
    		return retval;
    
    	sp = sctp_sk(sk);
    	ep = sp->ep;
    
    	SCTP_DEBUG_PRINTK("%s: (sk: %p, addrs: %p, addrcnt: %d)\n",
    
    			  __func__, sk, addrs, addrcnt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	list_for_each_entry(asoc, &ep->asocs, asocs) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (!asoc->peer.asconf_capable)
    			continue;
    
    		if (asoc->peer.addip_disabled_mask & SCTP_PARAM_DEL_IP)
    			continue;
    
    		if (!sctp_state(asoc, ESTABLISHED))
    			continue;
    
    		/* Check if any address in the packed array of addresses is
    
    		 * not present in the bind address list of the association.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 * If so, do not send the asconf chunk to its peer, but
    		 * continue with other associations.
    		 */
    		addr_buf = addrs;
    		for (i = 0; i < addrcnt; i++) {
    			laddr = (union sctp_addr *)addr_buf;
    			af = sctp_get_af_specific(laddr->v4.sin_family);
    			if (!af) {
    				retval = -EINVAL;
    				goto out;
    			}
    
    			if (!sctp_assoc_lookup_laddr(asoc, laddr))
    				break;
    
    			addr_buf += af->sockaddr_len;
    		}
    		if (i < addrcnt)
    			continue;
    
    		/* Find one address in the association's bind address list
    		 * that is not in the packed array of addresses. This is to
    		 * make sure that we do not delete all the addresses in the
    		 * association.
    		 */
    		bp = &asoc->base.bind_addr;
    		laddr = sctp_find_unmatch_addr(bp, (union sctp_addr *)addrs,
    					       addrcnt, sp);
    		if (!laddr)
    			continue;
    
    
    		/* We do not need RCU protection throughout this loop
    		 * because this is done under a socket lock from the
    		 * setsockopt call.
    		 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		chunk = sctp_make_asconf_update_ip(asoc, laddr, addrs, addrcnt,
    						   SCTP_PARAM_DEL_IP);
    		if (!chunk) {
    			retval = -ENOMEM;
    			goto out;
    		}
    
    
    		/* Reset use_as_src flag for the addresses in the bind address
    		 * list that are to be deleted.
    		 */
    		addr_buf = addrs;
    		for (i = 0; i < addrcnt; i++) {
    			laddr = (union sctp_addr *)addr_buf;
    			af = sctp_get_af_specific(laddr->v4.sin_family);
    
    			list_for_each_entry(saddr, &bp->address_list, list) {
    
    				if (sctp_cmp_addr_exact(&saddr->a, laddr))
    
    					saddr->state = SCTP_ADDR_DEL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		/* Update the route and saddr entries for all the transports
    		 * as some of the addresses in the bind address list are
    		 * about to be deleted and cannot be used as source addresses.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 */
    
    		list_for_each_entry(transport, &asoc->peer.transport_addr_list,
    					transports) {
    
    			dst_release(transport->dst);
    			sctp_transport_route(transport, NULL,
    					     sctp_sk(asoc->base.sk));
    		}
    
    		retval = sctp_send_asconf(asoc, chunk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    out:
    	return retval;
    }
    
    /* Helper for tunneling sctp_bindx() requests through sctp_setsockopt()
     *
     * API 8.1
     * int sctp_bindx(int sd, struct sockaddr *addrs, int addrcnt,
     *                int flags);
     *
     * If sd is an IPv4 socket, the addresses passed must be IPv4 addresses.
     * If the sd is an IPv6 socket, the addresses passed can either be IPv4
     * or IPv6 addresses.
     *
     * A single address may be specified as INADDR_ANY or IN6ADDR_ANY, see
     * Section 3.1.2 for this usage.
     *
     * addrs is a pointer to an array of one or more socket addresses. Each
     * address is contained in its appropriate structure (i.e. struct
     * sockaddr_in or struct sockaddr_in6) the family of the address type
    
    Ville Nuorvala's avatar
    Ville Nuorvala committed
     * must be used to distinguish the address length (note that this
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * representation is termed a "packed array" of addresses). The caller
     * specifies the number of addresses in the array with addrcnt.
     *
     * On success, sctp_bindx() returns 0. On failure, sctp_bindx() returns
     * -1, and sets errno to the appropriate error code.
     *
     * For SCTP, the port given in each socket address must be the same, or
     * sctp_bindx() will fail, setting errno to EINVAL.
     *
     * The flags parameter is formed from the bitwise OR of zero or more of
     * the following currently defined flags:
     *
     * SCTP_BINDX_ADD_ADDR
     *
     * SCTP_BINDX_REM_ADDR
     *
     * SCTP_BINDX_ADD_ADDR directs SCTP to add the given addresses to the
     * association, and SCTP_BINDX_REM_ADDR directs SCTP to remove the given
     * addresses from the association. The two flags are mutually exclusive;
     * if both are given, sctp_bindx() will fail with EINVAL. A caller may
     * not remove all addresses from an association; sctp_bindx() will
     * reject such an attempt with EINVAL.
     *
     * An application can use sctp_bindx(SCTP_BINDX_ADD_ADDR) to associate
     * additional addresses with an endpoint after calling bind().  Or use
     * sctp_bindx(SCTP_BINDX_REM_ADDR) to remove some addresses a listening
     * socket is associated with so that no new association accepted will be
     * associated with those addresses. If the endpoint supports dynamic
     * address a SCTP_BINDX_REM_ADDR or SCTP_BINDX_ADD_ADDR may cause a
     * endpoint to send the appropriate message to the peer to change the
     * peers address lists.
     *
     * Adding and removing addresses from a connected association is
     * optional functionality. Implementations that do not support this
     * functionality should return EOPNOTSUPP.
     *
     * Basically do nothing but copying the addresses from user to kernel
     * land and invoking either sctp_bindx_add() or sctp_bindx_rem() on the sk.
    
     * This is used for tunneling the sctp_bindx() request through sctp_setsockopt()
     * from userspace.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * We don't use copy_from_user() for optimization: we first do the
     * sanity checks (buffer size -fast- and access check-healthy
     * pointer); if all of those succeed, then we can alloc the memory
     * (expensive operation) needed to copy the data to kernel. Then we do
     * the copying without checking the user space area
     * (__copy_from_user()).
     *
     * On exit there is no need to do sockfd_put(), sys_setsockopt() does
     * it.
     *
     * sk        The sk of the socket
     * addrs     The pointer to the addresses in user land
     * addrssize Size of the addrs buffer
     * op        Operation to perform (add or remove, see the flags of
     *           sctp_bindx)
     *
     * Returns 0 if ok, <0 errno code on error.
     */
    SCTP_STATIC int sctp_setsockopt_bindx(struct sock* sk,
    				      struct sockaddr __user *addrs,
    				      int addrs_size, int op)
    {
    	struct sockaddr *kaddrs;
    	int err;
    	int addrcnt = 0;
    	int walk_size = 0;
    	struct sockaddr *sa_addr;
    	void *addr_buf;
    	struct sctp_af *af;
    
    	SCTP_DEBUG_PRINTK("sctp_setsocktopt_bindx: sk %p addrs %p"
    			  " addrs_size %d opt %d\n", sk, addrs, addrs_size, op);
    
    	if (unlikely(addrs_size <= 0))
    		return -EINVAL;
    
    	/* Check the user passed a healthy pointer.  */
    	if (unlikely(!access_ok(VERIFY_READ, addrs, addrs_size)))
    		return -EFAULT;
    
    	/* Alloc space for the address array in kernel memory.  */
    
    	kaddrs = kmalloc(addrs_size, GFP_KERNEL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (unlikely(!kaddrs))
    		return -ENOMEM;
    
    	if (__copy_from_user(kaddrs, addrs, addrs_size)) {
    		kfree(kaddrs);
    		return -EFAULT;
    	}
    
    
    	/* Walk through the addrs buffer and count the number of addresses. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	addr_buf = kaddrs;
    	while (walk_size < addrs_size) {
    		sa_addr = (struct sockaddr *)addr_buf;
    		af = sctp_get_af_specific(sa_addr->sa_family);
    
    		/* If the address family is not supported or if this address
    		 * causes the address buffer to overflow return EINVAL.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!af || (walk_size + af->sockaddr_len) > addrs_size) {
    			kfree(kaddrs);
    			return -EINVAL;
    		}
    		addrcnt++;
    		addr_buf += af->sockaddr_len;
    		walk_size += af->sockaddr_len;
    	}
    
    	/* Do the work. */
    	switch (op) {
    	case SCTP_BINDX_ADD_ADDR:
    		err = sctp_bindx_add(sk, kaddrs, addrcnt);
    		if (err)
    			goto out;
    		err = sctp_send_asconf_add_ip(sk, kaddrs, addrcnt);
    		break;
    
    	case SCTP_BINDX_REM_ADDR:
    		err = sctp_bindx_rem(sk, kaddrs, addrcnt);
    		if (err)
    			goto out;
    		err = sctp_send_asconf_del_ip(sk, kaddrs, addrcnt);
    		break;
    
    	default:
    		err = -EINVAL;
    		break;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    out:
    	kfree(kaddrs);
    
    	return err;
    }
    
    
    /* __sctp_connect(struct sock* sk, struct sockaddr *kaddrs, int addrs_size)
     *
     * Common routine for handling connect() and sctp_connectx().
     * Connect will come in with just a single address.
     */
    static int __sctp_connect(struct sock* sk,
    			  struct sockaddr *kaddrs,
    
    			  int addrs_size,
    			  sctp_assoc_t *assoc_id)
    
    {
    	struct sctp_sock *sp;
    	struct sctp_endpoint *ep;
    	struct sctp_association *asoc = NULL;
    	struct sctp_association *asoc2;
    	struct sctp_transport *transport;
    	union sctp_addr to;
    	struct sctp_af *af;
    	sctp_scope_t scope;
    	long timeo;
    	int err = 0;
    	int addrcnt = 0;
    	int walk_size = 0;
    
    	union sctp_addr *sa_addr = NULL;
    
    	void *addr_buf;
    
    
    	sp = sctp_sk(sk);
    	ep = sp->ep;
    
    	/* connect() cannot be done on a socket that is already in ESTABLISHED
    	 * state - UDP-style peeled off socket or a TCP-style socket that
    	 * is already connected.
    	 * It cannot be done even on a TCP-style listening socket.
    	 */
    	if (sctp_sstate(sk, ESTABLISHED) ||
    	    (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))) {
    		err = -EISCONN;
    		goto out_free;
    	}