Skip to content
Snippets Groups Projects
socket.c 195 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * 7.1.1 SCTP_RTOINFO
     *
     * The protocol parameters used to initialize and bound retransmission
     * timeout (RTO) are tunable. sctp_rtoinfo structure is used to access
     * and modify these parameters.
     * All parameters are time values, in milliseconds.  A value of 0, when
     * modifying the parameters, indicates that the current value should not
     * be changed.
     *
     */
    static int sctp_getsockopt_rtoinfo(struct sock *sk, int len,
    				char __user *optval,
    				int __user *optlen) {
    	struct sctp_rtoinfo rtoinfo;
    	struct sctp_association *asoc;
    
    
    	if (len < sizeof (struct sctp_rtoinfo))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	len = sizeof(struct sctp_rtoinfo);
    
    	if (copy_from_user(&rtoinfo, optval, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, rtoinfo.srto_assoc_id);
    
    	if (!asoc && rtoinfo.srto_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	/* Values corresponding to the specific association. */
    	if (asoc) {
    		rtoinfo.srto_initial = jiffies_to_msecs(asoc->rto_initial);
    		rtoinfo.srto_max = jiffies_to_msecs(asoc->rto_max);
    		rtoinfo.srto_min = jiffies_to_msecs(asoc->rto_min);
    	} else {
    		/* Values corresponding to the endpoint. */
    		struct sctp_sock *sp = sctp_sk(sk);
    
    		rtoinfo.srto_initial = sp->rtoinfo.srto_initial;
    		rtoinfo.srto_max = sp->rtoinfo.srto_max;
    		rtoinfo.srto_min = sp->rtoinfo.srto_min;
    	}
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	if (copy_to_user(optval, &rtoinfo, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    /*
     *
     * 7.1.2 SCTP_ASSOCINFO
     *
    
     * This option is used to tune the maximum retransmission attempts
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * of the association.
     * Returns an error if the new association retransmission value is
     * greater than the sum of the retransmission value  of the peer.
     * See [SCTP] for more information.
     *
     */
    static int sctp_getsockopt_associnfo(struct sock *sk, int len,
    				     char __user *optval,
    				     int __user *optlen)
    {
    
    	struct sctp_assocparams assocparams;
    	struct sctp_association *asoc;
    	struct list_head *pos;
    	int cnt = 0;
    
    
    	if (len < sizeof (struct sctp_assocparams))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	len = sizeof(struct sctp_assocparams);
    
    	if (copy_from_user(&assocparams, optval, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, assocparams.sasoc_assoc_id);
    
    	if (!asoc && assocparams.sasoc_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	/* Values correspoinding to the specific association */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		assocparams.sasoc_asocmaxrxt = asoc->max_retrans;
    		assocparams.sasoc_peer_rwnd = asoc->peer.rwnd;
    		assocparams.sasoc_local_rwnd = asoc->a_rwnd;
    		assocparams.sasoc_cookie_life = (asoc->cookie_life.tv_sec
    						* 1000) +
    						(asoc->cookie_life.tv_usec
    						/ 1000);
    
    		list_for_each(pos, &asoc->peer.transport_addr_list) {
    			cnt ++;
    		}
    
    		assocparams.sasoc_number_peer_destinations = cnt;
    	} else {
    		/* Values corresponding to the endpoint */
    		struct sctp_sock *sp = sctp_sk(sk);
    
    		assocparams.sasoc_asocmaxrxt = sp->assocparams.sasoc_asocmaxrxt;
    		assocparams.sasoc_peer_rwnd = sp->assocparams.sasoc_peer_rwnd;
    		assocparams.sasoc_local_rwnd = sp->assocparams.sasoc_local_rwnd;
    		assocparams.sasoc_cookie_life =
    					sp->assocparams.sasoc_cookie_life;
    		assocparams.sasoc_number_peer_destinations =
    					sp->assocparams.
    					sasoc_number_peer_destinations;
    	}
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	if (copy_to_user(optval, &assocparams, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    /*
     * 7.1.16 Set/clear IPv4 mapped addresses (SCTP_I_WANT_MAPPED_V4_ADDR)
     *
     * This socket option is a boolean flag which turns on or off mapped V4
     * addresses.  If this option is turned on and the socket is type
     * PF_INET6, then IPv4 addresses will be mapped to V6 representation.
     * If this option is turned off, then no mapping will be done of V4
     * addresses and a user will receive both PF_INET6 and PF_INET type
     * addresses on the socket.
     */
    static int sctp_getsockopt_mappedv4(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    	int val;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (len < sizeof(int))
    		return -EINVAL;
    
    	len = sizeof(int);
    	val = sp->v4mapped;
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    /*
     * 7.1.29.  Set or Get the default context (SCTP_CONTEXT)
     * (chapter and verse is quoted at sctp_setsockopt_context())
     */
    static int sctp_getsockopt_context(struct sock *sk, int len,
    				   char __user *optval, int __user *optlen)
    {
    	struct sctp_assoc_value params;
    	struct sctp_sock *sp;
    	struct sctp_association *asoc;
    
    
    	if (len < sizeof(struct sctp_assoc_value))
    
    	len = sizeof(struct sctp_assoc_value);
    
    
    	if (copy_from_user(&params, optval, len))
    		return -EFAULT;
    
    	sp = sctp_sk(sk);
    
    	if (params.assoc_id != 0) {
    		asoc = sctp_id2assoc(sk, params.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		params.assoc_value = asoc->default_rcv_context;
    	} else {
    		params.assoc_value = sp->default_rcv_context;
    	}
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &params, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
    
     * 8.1.16.  Get or Set the Maximum Fragmentation Size (SCTP_MAXSEG)
     * This option will get or set the maximum size to put in any outgoing
     * SCTP DATA chunk.  If a message is larger than this size it will be
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * fragmented by SCTP into the specified size.  Note that the underlying
     * SCTP implementation may fragment into smaller sized chunks when the
     * PMTU of the underlying association is smaller than the value set by
    
     * the user.  The default value for this option is '0' which indicates
     * the user is NOT limiting fragmentation and only the PMTU will effect
     * SCTP's choice of DATA chunk size.  Note also that values set larger
     * than the maximum size of an IP datagram will effectively let SCTP
     * control fragmentation (i.e. the same as setting this option to 0).
     *
     * The following structure is used to access and modify this parameter:
     *
     * struct sctp_assoc_value {
     *   sctp_assoc_t assoc_id;
     *   uint32_t assoc_value;
     * };
     *
     * assoc_id:  This parameter is ignored for one-to-one style sockets.
     *    For one-to-many style sockets this parameter indicates which
     *    association the user is performing an action upon.  Note that if
     *    this field's value is zero then the endpoints default value is
     *    changed (effecting future associations only).
     * assoc_value:  This parameter specifies the maximum size in bytes.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    static int sctp_getsockopt_maxseg(struct sock *sk, int len,
    				  char __user *optval, int __user *optlen)
    {
    
    	struct sctp_assoc_value params;
    	struct sctp_association *asoc;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (len == sizeof(int)) {
    		printk(KERN_WARNING
    		   "SCTP: Use of int in maxseg socket option deprecated\n");
    		printk(KERN_WARNING
    		   "SCTP: Use struct sctp_assoc_value instead\n");
    		params.assoc_id = 0;
    	} else if (len >= sizeof(struct sctp_assoc_value)) {
    		len = sizeof(struct sctp_assoc_value);
    		if (copy_from_user(&params, optval, sizeof(params)))
    			return -EFAULT;
    	} else
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	asoc = sctp_id2assoc(sk, params.assoc_id);
    	if (!asoc && params.assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc)
    		params.assoc_value = asoc->frag_point;
    	else
    		params.assoc_value = sctp_sk(sk)->user_frag;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	if (len == sizeof(int)) {
    		if (copy_to_user(optval, &params.assoc_value, len))
    			return -EFAULT;
    	} else {
    		if (copy_to_user(optval, &params, len))
    			return -EFAULT;
    	}
    
    /*
     * 7.1.24.  Get or set fragmented interleave (SCTP_FRAGMENT_INTERLEAVE)
     * (chapter and verse is quoted at sctp_setsockopt_fragment_interleave())
     */
    static int sctp_getsockopt_fragment_interleave(struct sock *sk, int len,
    					       char __user *optval, int __user *optlen)
    {
    	int val;
    
    	if (len < sizeof(int))
    		return -EINVAL;
    
    	len = sizeof(int);
    
    	val = sctp_sk(sk)->frag_interleave;
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    /*
     * 7.1.25.  Set or Get the sctp partial delivery point
     * (chapter and verse is quoted at sctp_setsockopt_partial_delivery_point())
     */
    static int sctp_getsockopt_partial_delivery_point(struct sock *sk, int len,
    						  char __user *optval,
    						  int __user *optlen)
    {
    
    
    	if (len < sizeof(u32))
    		return -EINVAL;
    
    	len = sizeof(u32);
    
    	val = sctp_sk(sk)->pd_point;
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return -ENOTSUPP;
    }
    
    
    /*
     * 7.1.28.  Set or Get the maximum burst (SCTP_MAX_BURST)
     * (chapter and verse is quoted at sctp_setsockopt_maxburst())
     */
    static int sctp_getsockopt_maxburst(struct sock *sk, int len,
    				    char __user *optval,
    				    int __user *optlen)
    {
    
    	struct sctp_assoc_value params;
    	struct sctp_sock *sp;
    	struct sctp_association *asoc;
    
    	if (len == sizeof(int)) {
    		printk(KERN_WARNING
    		   "SCTP: Use of int in max_burst socket option deprecated\n");
    		printk(KERN_WARNING
    		   "SCTP: Use struct sctp_assoc_value instead\n");
    		params.assoc_id = 0;
    
    	} else if (len >= sizeof(struct sctp_assoc_value)) {
    		len = sizeof(struct sctp_assoc_value);
    
    		if (copy_from_user(&params, optval, len))
    			return -EFAULT;
    	} else
    		return -EINVAL;
    
    	sp = sctp_sk(sk);
    
    	if (params.assoc_id != 0) {
    		asoc = sctp_id2assoc(sk, params.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		params.assoc_value = asoc->max_burst;
    	} else
    		params.assoc_value = sp->max_burst;
    
    	if (len == sizeof(int)) {
    		if (copy_to_user(optval, &params.assoc_value, len))
    			return -EFAULT;
    	} else {
    		if (copy_to_user(optval, &params, len))
    			return -EFAULT;
    	}
    
    	return 0;
    
    static int sctp_getsockopt_hmac_ident(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    
    	struct sctp_hmacalgo  __user *p = (void __user *)optval;
    
    	struct sctp_hmac_algo_param *hmacs;
    
    	__u16 data_len = 0;
    	u32 num_idents;
    
    	if (!sctp_auth_enable)
    		return -EACCES;
    
    
    	hmacs = sctp_sk(sk)->ep->auth_hmacs_list;
    
    	data_len = ntohs(hmacs->param_hdr.length) - sizeof(sctp_paramhdr_t);
    
    	if (len < sizeof(struct sctp_hmacalgo) + data_len)
    
    
    	len = sizeof(struct sctp_hmacalgo) + data_len;
    	num_idents = data_len / sizeof(u16);
    
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	if (put_user(num_idents, &p->shmac_num_idents))
    		return -EFAULT;
    	if (copy_to_user(p->shmac_idents, hmacs->hmac_ids, data_len))
    
    		return -EFAULT;
    	return 0;
    }
    
    static int sctp_getsockopt_active_key(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    	struct sctp_authkeyid val;
    	struct sctp_association *asoc;
    
    
    	if (!sctp_auth_enable)
    		return -EACCES;
    
    
    	if (len < sizeof(struct sctp_authkeyid))
    		return -EINVAL;
    	if (copy_from_user(&val, optval, sizeof(struct sctp_authkeyid)))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, val.scact_assoc_id);
    	if (!asoc && val.scact_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc)
    		val.scact_keynumber = asoc->active_key_id;
    	else
    		val.scact_keynumber = sctp_sk(sk)->ep->active_key_id;
    
    
    	len = sizeof(struct sctp_authkeyid);
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    
    	return 0;
    }
    
    static int sctp_getsockopt_peer_auth_chunks(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    
    Al Viro's avatar
    Al Viro committed
    	struct sctp_authchunks __user *p = (void __user *)optval;
    
    	struct sctp_authchunks val;
    	struct sctp_association *asoc;
    	struct sctp_chunks_param *ch;
    
    	u32    num_chunks = 0;
    
    	if (!sctp_auth_enable)
    		return -EACCES;
    
    	if (len < sizeof(struct sctp_authchunks))
    
    	if (copy_from_user(&val, optval, sizeof(struct sctp_authchunks)))
    
    Al Viro's avatar
    Al Viro committed
    	to = p->gauth_chunks;
    
    	asoc = sctp_id2assoc(sk, val.gauth_assoc_id);
    	if (!asoc)
    		return -EINVAL;
    
    	ch = asoc->peer.peer_chunks;
    
    
    	/* See if the user provided enough room for all the data */
    
    	num_chunks = ntohs(ch->param_hdr.length) - sizeof(sctp_paramhdr_t);
    	if (len < num_chunks)
    
    	if (copy_to_user(to, ch->chunks, num_chunks))
    
    num:
    	len = sizeof(struct sctp_authchunks) + num_chunks;
    	if (put_user(len, optlen)) return -EFAULT;
    
    	if (put_user(num_chunks, &p->gauth_number_of_chunks))
    		return -EFAULT;
    
    	return 0;
    }
    
    static int sctp_getsockopt_local_auth_chunks(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    
    Al Viro's avatar
    Al Viro committed
    	struct sctp_authchunks __user *p = (void __user *)optval;
    
    	struct sctp_authchunks val;
    	struct sctp_association *asoc;
    	struct sctp_chunks_param *ch;
    
    	u32    num_chunks = 0;
    
    	if (!sctp_auth_enable)
    		return -EACCES;
    
    	if (len < sizeof(struct sctp_authchunks))
    
    	if (copy_from_user(&val, optval, sizeof(struct sctp_authchunks)))
    
    Al Viro's avatar
    Al Viro committed
    	to = p->gauth_chunks;
    
    	asoc = sctp_id2assoc(sk, val.gauth_assoc_id);
    	if (!asoc && val.gauth_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc)
    		ch = (struct sctp_chunks_param*)asoc->c.auth_chunks;
    	else
    		ch = sctp_sk(sk)->ep->auth_chunk_list;
    
    
    	num_chunks = ntohs(ch->param_hdr.length) - sizeof(sctp_paramhdr_t);
    
    	if (len < sizeof(struct sctp_authchunks) + num_chunks)
    
    	if (copy_to_user(to, ch->chunks, num_chunks))
    		return -EFAULT;
    num:
    	len = sizeof(struct sctp_authchunks) + num_chunks;
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	if (put_user(num_chunks, &p->gauth_number_of_chunks))
    		return -EFAULT;
    
    /*
     * 8.2.5.  Get the Current Number of Associations (SCTP_GET_ASSOC_NUMBER)
     * This option gets the current number of associations that are attached
     * to a one-to-many style socket.  The option value is an uint32_t.
     */
    static int sctp_getsockopt_assoc_number(struct sock *sk, int len,
    				    char __user *optval, int __user *optlen)
    {
    	struct sctp_sock *sp = sctp_sk(sk);
    	struct sctp_association *asoc;
    	u32 val = 0;
    
    	if (sctp_style(sk, TCP))
    		return -EOPNOTSUPP;
    
    	if (len < sizeof(u32))
    		return -EINVAL;
    
    	len = sizeof(u32);
    
    	list_for_each_entry(asoc, &(sp->ep->asocs), asocs) {
    		val++;
    	}
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
    				char __user *optval, int __user *optlen)
    {
    	int retval = 0;
    	int len;
    
    
    	SCTP_DEBUG_PRINTK("sctp_getsockopt(sk: %p... optname: %d)\n",
    			  sk, optname);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* I can hardly begin to describe how wrong this is.  This is
    	 * so broken as to be worse than useless.  The API draft
    	 * REALLY is NOT helpful here...  I am not convinced that the
    	 * semantics of getsockopt() with a level OTHER THAN SOL_SCTP
    	 * are at all well-founded.
    	 */
    	if (level != SOL_SCTP) {
    		struct sctp_af *af = sctp_sk(sk)->pf->af;
    
    		retval = af->getsockopt(sk, level, optname, optval, optlen);
    		return retval;
    	}
    
    	if (get_user(len, optlen))
    		return -EFAULT;
    
    	sctp_lock_sock(sk);
    
    	switch (optname) {
    	case SCTP_STATUS:
    		retval = sctp_getsockopt_sctp_status(sk, len, optval, optlen);
    		break;
    	case SCTP_DISABLE_FRAGMENTS:
    		retval = sctp_getsockopt_disable_fragments(sk, len, optval,
    							   optlen);
    		break;
    	case SCTP_EVENTS:
    		retval = sctp_getsockopt_events(sk, len, optval, optlen);
    		break;
    	case SCTP_AUTOCLOSE:
    		retval = sctp_getsockopt_autoclose(sk, len, optval, optlen);
    		break;
    	case SCTP_SOCKOPT_PEELOFF:
    		retval = sctp_getsockopt_peeloff(sk, len, optval, optlen);
    		break;
    	case SCTP_PEER_ADDR_PARAMS:
    		retval = sctp_getsockopt_peer_addr_params(sk, len, optval,
    							  optlen);
    		break;
    
    	case SCTP_DELAYED_ACK:
    		retval = sctp_getsockopt_delayed_ack(sk, len, optval,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	case SCTP_INITMSG:
    		retval = sctp_getsockopt_initmsg(sk, len, optval, optlen);
    		break;
    
    	case SCTP_GET_PEER_ADDRS_NUM_OLD:
    		retval = sctp_getsockopt_peer_addrs_num_old(sk, len, optval,
    							    optlen);
    		break;
    	case SCTP_GET_LOCAL_ADDRS_NUM_OLD:
    		retval = sctp_getsockopt_local_addrs_num_old(sk, len, optval,
    							     optlen);
    		break;
    	case SCTP_GET_PEER_ADDRS_OLD:
    		retval = sctp_getsockopt_peer_addrs_old(sk, len, optval,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    							optlen);
    		break;
    
    	case SCTP_GET_LOCAL_ADDRS_OLD:
    		retval = sctp_getsockopt_local_addrs_old(sk, len, optval,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    							 optlen);
    		break;
    	case SCTP_GET_PEER_ADDRS:
    		retval = sctp_getsockopt_peer_addrs(sk, len, optval,
    						    optlen);
    		break;
    	case SCTP_GET_LOCAL_ADDRS:
    		retval = sctp_getsockopt_local_addrs(sk, len, optval,
    						     optlen);
    		break;
    
    	case SCTP_SOCKOPT_CONNECTX3:
    		retval = sctp_getsockopt_connectx3(sk, len, optval, optlen);
    		break;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	case SCTP_DEFAULT_SEND_PARAM:
    		retval = sctp_getsockopt_default_send_param(sk, len,
    							    optval, optlen);
    		break;
    	case SCTP_PRIMARY_ADDR:
    		retval = sctp_getsockopt_primary_addr(sk, len, optval, optlen);
    		break;
    	case SCTP_NODELAY:
    		retval = sctp_getsockopt_nodelay(sk, len, optval, optlen);
    		break;
    	case SCTP_RTOINFO:
    		retval = sctp_getsockopt_rtoinfo(sk, len, optval, optlen);
    		break;
    	case SCTP_ASSOCINFO:
    		retval = sctp_getsockopt_associnfo(sk, len, optval, optlen);
    		break;
    	case SCTP_I_WANT_MAPPED_V4_ADDR:
    		retval = sctp_getsockopt_mappedv4(sk, len, optval, optlen);
    		break;
    	case SCTP_MAXSEG:
    		retval = sctp_getsockopt_maxseg(sk, len, optval, optlen);
    		break;
    	case SCTP_GET_PEER_ADDR_INFO:
    		retval = sctp_getsockopt_peer_addr_info(sk, len, optval,
    							optlen);
    		break;
    
    	case SCTP_ADAPTATION_LAYER:
    		retval = sctp_getsockopt_adaptation_layer(sk, len, optval,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    							optlen);
    		break;
    
    	case SCTP_CONTEXT:
    		retval = sctp_getsockopt_context(sk, len, optval, optlen);
    		break;
    
    	case SCTP_FRAGMENT_INTERLEAVE:
    		retval = sctp_getsockopt_fragment_interleave(sk, len, optval,
    							     optlen);
    		break;
    
    	case SCTP_PARTIAL_DELIVERY_POINT:
    		retval = sctp_getsockopt_partial_delivery_point(sk, len, optval,
    								optlen);
    		break;
    
    	case SCTP_MAX_BURST:
    		retval = sctp_getsockopt_maxburst(sk, len, optval, optlen);
    		break;
    
    	case SCTP_AUTH_KEY:
    	case SCTP_AUTH_CHUNK:
    	case SCTP_AUTH_DELETE_KEY:
    		retval = -EOPNOTSUPP;
    		break;
    	case SCTP_HMAC_IDENT:
    		retval = sctp_getsockopt_hmac_ident(sk, len, optval, optlen);
    		break;
    	case SCTP_AUTH_ACTIVE_KEY:
    		retval = sctp_getsockopt_active_key(sk, len, optval, optlen);
    		break;
    	case SCTP_PEER_AUTH_CHUNKS:
    		retval = sctp_getsockopt_peer_auth_chunks(sk, len, optval,
    							optlen);
    		break;
    	case SCTP_LOCAL_AUTH_CHUNKS:
    		retval = sctp_getsockopt_local_auth_chunks(sk, len, optval,
    							optlen);
    		break;
    
    	case SCTP_GET_ASSOC_NUMBER:
    		retval = sctp_getsockopt_assoc_number(sk, len, optval, optlen);
    		break;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	default:
    		retval = -ENOPROTOOPT;
    		break;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sctp_release_sock(sk);
    	return retval;
    }
    
    static void sctp_hash(struct sock *sk)
    {
    	/* STUB */
    }
    
    static void sctp_unhash(struct sock *sk)
    {
    	/* STUB */
    }
    
    /* Check if port is acceptable.  Possibly find first available port.
     *
     * The port hash table (contained in the 'global' SCTP protocol storage
     * returned by struct sctp_protocol *sctp_get_protocol()). The hash
     * table is an array of 4096 lists (sctp_bind_hashbucket). Each
     * list (the list number is the port number hashed out, so as you
     * would expect from a hash function, all the ports in a given list have
     * such a number that hashes out to the same list number; you were
     * expecting that, right?); so each list has a set of ports, with a
     * link to the socket (struct sock) that uses it, the port number and
     * a fastreuse flag (FIXME: NPI ipg).
     */
    static struct sctp_bind_bucket *sctp_bucket_create(
    	struct sctp_bind_hashbucket *head, unsigned short snum);
    
    static long sctp_get_port_local(struct sock *sk, union sctp_addr *addr)
    {
    	struct sctp_bind_hashbucket *head; /* hash list */
    	struct sctp_bind_bucket *pp; /* hash list port iterator */
    
    	struct hlist_node *node;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned short snum;
    	int ret;
    
    
    	snum = ntohs(addr->v4.sin_port);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	SCTP_DEBUG_PRINTK("sctp_get_port() begins, snum=%d\n", snum);
    	sctp_local_bh_disable();
    
    	if (snum == 0) {
    
    		/* Search for an available port. */
    
    		int low, high, remaining, index;
    		unsigned int rover;
    
    		inet_get_local_port_range(&low, &high);
    		remaining = (high - low) + 1;
    		rover = net_random() % remaining + low;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		do {
    			rover++;
    			if ((rover < low) || (rover > high))
    				rover = low;
    			index = sctp_phashfn(rover);
    			head = &sctp_port_hashtable[index];
    			sctp_spin_lock(&head->lock);
    
    			sctp_for_each_hentry(pp, node, &head->chain)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				if (pp->port == rover)
    					goto next;
    			break;
    		next:
    			sctp_spin_unlock(&head->lock);
    		} while (--remaining > 0);
    
    		/* Exhausted local port range during search? */
    		ret = 1;
    		if (remaining <= 0)
    			goto fail;
    
    		/* OK, here is the one we will use.  HEAD (the port
    		 * hash table list entry) is non-NULL and we hold it's
    		 * mutex.
    		 */
    		snum = rover;
    	} else {
    		/* We are given an specific port number; we verify
    		 * that it is not being used. If it is used, we will
    		 * exahust the search in the hash list corresponding
    		 * to the port number (snum) - we detect that with the
    		 * port iterator, pp being NULL.
    		 */
    		head = &sctp_port_hashtable[sctp_phashfn(snum)];
    		sctp_spin_lock(&head->lock);
    
    		sctp_for_each_hentry(pp, node, &head->chain) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (pp->port == snum)
    				goto pp_found;
    		}
    	}
    	pp = NULL;
    	goto pp_not_found;
    pp_found:
    	if (!hlist_empty(&pp->owner)) {
    		/* We had a port hash table hit - there is an
    		 * available port (pp != NULL) and it is being
    		 * used by other socket (pp->owner not empty); that other
    		 * socket is going to be sk2.
    		 */
    		int reuse = sk->sk_reuse;
    		struct sock *sk2;
    		struct hlist_node *node;
    
    		SCTP_DEBUG_PRINTK("sctp_get_port() found a possible match\n");
    
    		if (pp->fastreuse && sk->sk_reuse &&
    			sk->sk_state != SCTP_SS_LISTENING)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto success;
    
    		/* Run through the list of sockets bound to the port
    		 * (pp->port) [via the pointers bind_next and
    		 * bind_pprev in the struct sock *sk2 (pp->sk)]. On each one,
    		 * we get the endpoint they describe and run through
    		 * the endpoint's list of IP (v4 or v6) addresses,
    		 * comparing each of the addresses with the address of
    		 * the socket sk. If we find a match, then that means
    		 * that this port/socket (sk) combination are already
    		 * in an endpoint.
    		 */
    		sk_for_each_bound(sk2, node, &pp->owner) {
    			struct sctp_endpoint *ep2;
    			ep2 = sctp_sk(sk2)->ep;
    
    
    			if (sk == sk2 ||
    			    (reuse && sk2->sk_reuse &&
    			     sk2->sk_state != SCTP_SS_LISTENING))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				continue;
    
    
    			if (sctp_bind_addr_conflict(&ep2->base.bind_addr, addr,
    						 sctp_sk(sk2), sctp_sk(sk))) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				ret = (long)sk2;
    				goto fail_unlock;
    			}
    		}
    		SCTP_DEBUG_PRINTK("sctp_get_port(): Found a match\n");
    	}
    pp_not_found:
    	/* If there was a hash table miss, create a new port.  */
    	ret = 1;
    	if (!pp && !(pp = sctp_bucket_create(head, snum)))
    		goto fail_unlock;
    
    	/* In either case (hit or miss), make sure fastreuse is 1 only
    	 * if sk->sk_reuse is too (that is, if the caller requested
    	 * SO_REUSEADDR on this socket -sk-).
    	 */
    
    	if (hlist_empty(&pp->owner)) {
    		if (sk->sk_reuse && sk->sk_state != SCTP_SS_LISTENING)
    			pp->fastreuse = 1;
    		else
    			pp->fastreuse = 0;
    	} else if (pp->fastreuse &&
    		(!sk->sk_reuse || sk->sk_state == SCTP_SS_LISTENING))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		pp->fastreuse = 0;
    
    	/* We are set, so fill up all the data in the hash table
    	 * entry, tie the socket list information with the rest of the
    	 * sockets FIXME: Blurry, NPI (ipg).
    	 */
    success:
    	if (!sctp_sk(sk)->bind_hash) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		sk_add_bind_node(sk, &pp->owner);
    		sctp_sk(sk)->bind_hash = pp;
    	}
    	ret = 0;
    
    fail_unlock:
    	sctp_spin_unlock(&head->lock);
    
    fail:
    	sctp_local_bh_enable();
    	return ret;
    }
    
    /* Assign a 'snum' port to the socket.  If snum == 0, an ephemeral
     * port is requested.
     */
    static int sctp_get_port(struct sock *sk, unsigned short snum)
    {
    	long ret;
    	union sctp_addr addr;
    	struct sctp_af *af = sctp_sk(sk)->pf->af;
    
    	/* Set up a dummy address struct from the sk. */
    	af->from_sk(&addr, sk);
    	addr.v4.sin_port = htons(snum);
    
    	/* Note: sk->sk_num gets filled in if ephemeral port request. */
    	ret = sctp_get_port_local(sk, &addr);
    
    	return (ret ? 1 : 0);
    }
    
    /*
    
     *  Move a socket to LISTENING state.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    SCTP_STATIC int sctp_listen_start(struct sock *sk, int backlog)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct sctp_sock *sp = sctp_sk(sk);
    	struct sctp_endpoint *ep = sp->ep;
    
    	struct crypto_hash *tfm = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Allocate HMAC for generating cookie. */
    	if (!sctp_sk(sk)->hmac && sctp_hmac_alg) {
    		tfm = crypto_alloc_hash(sctp_hmac_alg, 0, CRYPTO_ALG_ASYNC);
    		if (IS_ERR(tfm)) {
    			if (net_ratelimit()) {
    				printk(KERN_INFO
    				       "SCTP: failed to load transform for %s: %ld\n",
    					sctp_hmac_alg, PTR_ERR(tfm));
    			}
    			return -ENOSYS;
    		}
    		sctp_sk(sk)->hmac = tfm;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	/*
    	 * If a bind() or sctp_bindx() is not called prior to a listen()
    	 * call that allows new associations to be accepted, the system
    	 * picks an ephemeral port and will choose an address set equivalent
    	 * to binding with a wildcard address.
    	 *
    	 * This is not currently spelled out in the SCTP sockets
    	 * extensions draft, but follows the practice as seen in TCP
    	 * sockets.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    
    	sk->sk_state = SCTP_SS_LISTENING;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!ep->base.bind_addr.port) {
    		if (sctp_autobind(sk))
    			return -EAGAIN;
    
    	} else {
    		if (sctp_get_port(sk, inet_sk(sk)->num)) {
    			sk->sk_state = SCTP_SS_CLOSED;
    			return -EADDRINUSE;
    		}
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	sk->sk_max_ack_backlog = backlog;
    	sctp_hash_endpoint(ep);
    	return 0;
    }
    
    /*
    
     * 4.1.3 / 5.1.3 listen()
     *
     *   By default, new associations are not accepted for UDP style sockets.
     *   An application uses listen() to mark a socket as being able to
     *   accept new associations.
     *
     *   On TCP style sockets, applications use listen() to ready the SCTP
     *   endpoint for accepting inbound associations.
     *
     *   On both types of endpoints a backlog of '0' disables listening.
     *
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *  Move a socket to LISTENING state.
     */
    int sctp_inet_listen(struct socket *sock, int backlog)
    {
    	struct sock *sk = sock->sk;
    
    	struct sctp_endpoint *ep = sctp_sk(sk)->ep;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int err = -EINVAL;
    
    	if (unlikely(backlog < 0))
    
    		return err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sctp_lock_sock(sk);
    
    
    	/* Peeled-off sockets are not allowed to listen().  */
    	if (sctp_style(sk, UDP_HIGH_BANDWIDTH))
    		goto out;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (sock->state != SS_UNCONNECTED)
    		goto out;
    
    
    	/* If backlog is zero, disable listening. */
    	if (!backlog) {
    		if (sctp_sstate(sk, CLOSED))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out;
    
    
    		err = 0;
    		sctp_unhash_endpoint(ep);
    		sk->sk_state = SCTP_SS_CLOSED;
    		if (sk->sk_reuse)
    			sctp_sk(sk)->bind_hash->fastreuse = 1;
    		goto out;
    
    	/* If we are already listening, just update the backlog */
    	if (sctp_sstate(sk, LISTENING))
    		sk->sk_max_ack_backlog = backlog;
    	else {
    		err = sctp_listen_start(sk, backlog);
    		if (err)
    			goto out;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    out:
    	sctp_release_sock(sk);
    	return err;
    }
    
    /*
     * This function is done by modeling the current datagram_poll() and the
     * tcp_poll().  Note that, based on these implementations, we don't
     * lock the socket in this function, even though it seems that,
     * ideally, locking or some other mechanisms can be used to ensure
    
     * the integrity of the counters (sndbuf and wmem_alloc) used