Skip to content
Snippets Groups Projects
socket.c 174 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    	list_for_each(pos, &asoc->peer.transport_addr_list) {
    		from = list_entry(pos, struct sctp_transport, transports);
    
    		memcpy(&temp, &from->ipaddr, sizeof(temp));
    
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp, &temp);
    		addrlen = sctp_get_af_specific(sk->sk_family)->sockaddr_len;
    
    			return -ENOMEM;
    		if (copy_to_user(to, &temp, addrlen))
    			return -EFAULT;
    		to += addrlen;
    		cnt++;
    		space_left -= addrlen;
    	}
    
    	if (put_user(cnt, &((struct sctp_getaddrs __user *)optval)->addr_num))
    		return -EFAULT;
    	bytes_copied = ((char __user *)to) - optval;
    	if (put_user(bytes_copied, optlen))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    	return 0;
    }
    
    
    static int sctp_getsockopt_local_addrs_num_old(struct sock *sk, int len,
    					       char __user *optval,
    					       int __user *optlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	sctp_assoc_t id;
    	struct sctp_bind_addr *bp;
    	struct sctp_association *asoc;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_sockaddr_entry *addr;
    	rwlock_t *addr_lock;
    	int cnt = 0;
    
    	if (len != sizeof(sctp_assoc_t))
    		return -EINVAL;
    
    	if (copy_from_user(&id, optval, sizeof(sctp_assoc_t)))
    		return -EFAULT;
    
    	/*
    	 *  For UDP-style sockets, id specifies the association to query.
    	 *  If the id field is set to the value '0' then the locally bound
    	 *  addresses are returned without regard to any particular
    	 *  association.
    	 */
    	if (0 == id) {
    		bp = &sctp_sk(sk)->ep->base.bind_addr;
    		addr_lock = &sctp_sk(sk)->ep->base.addr_lock;
    	} else {
    		asoc = sctp_id2assoc(sk, id);
    		if (!asoc)
    			return -EINVAL;
    		bp = &asoc->base.bind_addr;
    		addr_lock = &asoc->base.addr_lock;
    	}
    
    	sctp_read_lock(addr_lock);
    
    	/* If the endpoint is bound to 0.0.0.0 or ::0, count the valid
    	 * addresses from the global local address list.
    	 */
    	if (sctp_list_single_entry(&bp->address_list)) {
    		addr = list_entry(bp->address_list.next,
    				  struct sctp_sockaddr_entry, list);
    
    		if (sctp_is_any(&addr->a)) {
    
    			list_for_each_safe(pos, temp, &sctp_local_addr_list) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				addr = list_entry(pos,
    						  struct sctp_sockaddr_entry,
    						  list);
    
    				if ((PF_INET == sk->sk_family) &&
    
    				    (AF_INET6 == addr->a.sa.sa_family))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					continue;
    				cnt++;
    			}
    		} else {
    			cnt = 1;
    		}
    		goto done;
    	}
    
    	list_for_each(pos, &bp->address_list) {
    		cnt ++;
    	}
    
    done:
    	sctp_read_unlock(addr_lock);
    	return cnt;
    }
    
    /* Helper function that copies local addresses to user and returns the number
     * of addresses copied.
     */
    
    static int sctp_copy_laddrs_old(struct sock *sk, __u16 port,
    					int max_addrs, void *to,
    					int *bytes_copied)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_sockaddr_entry *addr;
    	union sctp_addr temp;
    	int cnt = 0;
    	int addrlen;
    
    
    	list_for_each_safe(pos, next, &sctp_local_addr_list) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		addr = list_entry(pos, struct sctp_sockaddr_entry, list);
    
    		if ((PF_INET == sk->sk_family) &&
    
    		    (AF_INET6 == addr->a.sa.sa_family))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			continue;
    
    		memcpy(&temp, &addr->a, sizeof(temp));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
    								&temp);
    		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		to += addrlen;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		cnt ++;
    		if (cnt >= max_addrs) break;
    	}
    
    	return cnt;
    }
    
    
    static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to,
    			    size_t space_left, int *bytes_copied)
    
    	struct sctp_sockaddr_entry *addr;
    	union sctp_addr temp;
    	int cnt = 0;
    	int addrlen;
    
    
    	list_for_each_safe(pos, next, &sctp_local_addr_list) {
    
    		addr = list_entry(pos, struct sctp_sockaddr_entry, list);
    
    		if ((PF_INET == sk->sk_family) &&
    
    		    (AF_INET6 == addr->a.sa.sa_family))
    
    		memcpy(&temp, &addr->a, sizeof(temp));
    
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
    								&temp);
    		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
    
    	}
    
    	return cnt;
    }
    
    /* Old API for getting list of local addresses. Does not work for 32-bit
     * programs running on a 64-bit kernel
     */
    static int sctp_getsockopt_local_addrs_old(struct sock *sk, int len,
    					   char __user *optval, int __user *optlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct sctp_bind_addr *bp;
    	struct sctp_association *asoc;
    	struct list_head *pos;
    	int cnt = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_sockaddr_entry *addr;
    	void __user *to;
    	union sctp_addr temp;
    	struct sctp_sock *sp = sctp_sk(sk);
    	int addrlen;
    	rwlock_t *addr_lock;
    	int err = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	if (copy_from_user(&getaddrs, optval, sizeof(struct sctp_getaddrs_old)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    	if (getaddrs.addr_num <= 0) return -EINVAL;
    	/*
    	 *  For UDP-style sockets, id specifies the association to query.
    	 *  If the id field is set to the value '0' then the locally bound
    	 *  addresses are returned without regard to any particular
    	 *  association.
    	 */
    	if (0 == getaddrs.assoc_id) {
    		bp = &sctp_sk(sk)->ep->base.bind_addr;
    		addr_lock = &sctp_sk(sk)->ep->base.addr_lock;
    	} else {
    		asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		bp = &asoc->base.bind_addr;
    		addr_lock = &asoc->base.addr_lock;
    	}
    
    	to = getaddrs.addrs;
    
    
    	/* Allocate space for a local instance of packed array to hold all
    	 * the data.  We store addresses here first and then put write them
    	 * to the user in one shot.
    	 */
    	addrs = kmalloc(sizeof(union sctp_addr) * getaddrs.addr_num,
    			GFP_KERNEL);
    	if (!addrs)
    		return -ENOMEM;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	sctp_read_lock(addr_lock);
    
    	/* If the endpoint is bound to 0.0.0.0 or ::0, get the valid
    	 * addresses from the global local address list.
    	 */
    	if (sctp_list_single_entry(&bp->address_list)) {
    		addr = list_entry(bp->address_list.next,
    				  struct sctp_sockaddr_entry, list);
    
    		if (sctp_is_any(&addr->a)) {
    
    			cnt = sctp_copy_laddrs_old(sk, bp->port,
    						   getaddrs.addr_num,
    						   addrs, &bytes_copied);
    
    			goto copy_getaddrs;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	list_for_each(pos, &bp->address_list) {
    		addr = list_entry(pos, struct sctp_sockaddr_entry, list);
    
    		memcpy(&temp, &addr->a, sizeof(temp));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp, &temp);
    		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
    
    		memcpy(buf, &temp, addrlen);
    		buf += addrlen;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		cnt ++;
    		if (cnt >= getaddrs.addr_num) break;
    	}
    
    copy_getaddrs:
    
    	sctp_read_unlock(addr_lock);
    
    	/* copy the entire address list into the user provided space */
    	if (copy_to_user(to, addrs, bytes_copied)) {
    		err = -EFAULT;
    		goto error;
    	}
    
    	/* copy the leading structure back to user */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	getaddrs.addr_num = cnt;
    
    	if (copy_to_user(optval, &getaddrs, sizeof(struct sctp_getaddrs_old)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		err = -EFAULT;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    
    static int sctp_getsockopt_local_addrs(struct sock *sk, int len,
    				       char __user *optval, int __user *optlen)
    {
    	struct sctp_bind_addr *bp;
    	struct sctp_association *asoc;
    	struct list_head *pos;
    	int cnt = 0;
    	struct sctp_getaddrs getaddrs;
    	struct sctp_sockaddr_entry *addr;
    	void __user *to;
    	union sctp_addr temp;
    	struct sctp_sock *sp = sctp_sk(sk);
    	int addrlen;
    	rwlock_t *addr_lock;
    	int err = 0;
    	size_t space_left;
    
    
    	if (len <= sizeof(struct sctp_getaddrs))
    		return -EINVAL;
    
    	if (copy_from_user(&getaddrs, optval, sizeof(struct sctp_getaddrs)))
    		return -EFAULT;
    
    	/*
    	 *  For UDP-style sockets, id specifies the association to query.
    	 *  If the id field is set to the value '0' then the locally bound
    	 *  addresses are returned without regard to any particular
    	 *  association.
    	 */
    	if (0 == getaddrs.assoc_id) {
    		bp = &sctp_sk(sk)->ep->base.bind_addr;
    		addr_lock = &sctp_sk(sk)->ep->base.addr_lock;
    	} else {
    		asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		bp = &asoc->base.bind_addr;
    		addr_lock = &asoc->base.addr_lock;
    	}
    
    	to = optval + offsetof(struct sctp_getaddrs,addrs);
    	space_left = len - sizeof(struct sctp_getaddrs) -
    			 offsetof(struct sctp_getaddrs,addrs);
    
    	addrs = kmalloc(space_left, GFP_KERNEL);
    	if (!addrs)
    		return -ENOMEM;
    
    
    	sctp_read_lock(addr_lock);
    
    	/* If the endpoint is bound to 0.0.0.0 or ::0, get the valid
    	 * addresses from the global local address list.
    	 */
    	if (sctp_list_single_entry(&bp->address_list)) {
    		addr = list_entry(bp->address_list.next,
    				  struct sctp_sockaddr_entry, list);
    
    		if (sctp_is_any(&addr->a)) {
    
    			cnt = sctp_copy_laddrs(sk, bp->port, addrs,
    						space_left, &bytes_copied);
    
    			goto copy_getaddrs;
    
    	list_for_each(pos, &bp->address_list) {
    		addr = list_entry(pos, struct sctp_sockaddr_entry, list);
    
    		memcpy(&temp, &addr->a, sizeof(temp));
    
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp, &temp);
    		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
    
    		if (space_left < addrlen) {
    			err =  -ENOMEM; /*fixme: right error?*/
    			goto error;
    
    		memcpy(buf, &temp, addrlen);
    		buf += addrlen;
    
    	sctp_read_unlock(addr_lock);
    
    	if (copy_to_user(to, addrs, bytes_copied)) {
    		err = -EFAULT;
    		goto error;
    	}
    
    	if (put_user(cnt, &((struct sctp_getaddrs __user *)optval)->addr_num))
    		return -EFAULT;
    	if (put_user(bytes_copied, optlen))
    		return -EFAULT;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* 7.1.10 Set Primary Address (SCTP_PRIMARY_ADDR)
     *
     * Requests that the local SCTP stack use the enclosed peer address as
     * the association primary.  The enclosed address must be one of the
     * association peer's addresses.
     */
    static int sctp_getsockopt_primary_addr(struct sock *sk, int len,
    					char __user *optval, int __user *optlen)
    {
    	struct sctp_prim prim;
    	struct sctp_association *asoc;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (len != sizeof(struct sctp_prim))
    		return -EINVAL;
    
    	if (copy_from_user(&prim, optval, sizeof(struct sctp_prim)))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, prim.ssp_assoc_id);
    	if (!asoc)
    		return -EINVAL;
    
    	if (!asoc->peer.primary_path)
    		return -ENOTCONN;
    
    	memcpy(&prim.ssp_addr, &asoc->peer.primary_path->ipaddr,
    		asoc->peer.primary_path->af_specific->sockaddr_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp,
    			(union sctp_addr *)&prim.ssp_addr);
    
    	if (copy_to_user(optval, &prim, sizeof(struct sctp_prim)))
    		return -EFAULT;
    
    	return 0;
    }
    
    /*
    
     * 7.1.11  Set Adaptation Layer Indicator (SCTP_ADAPTATION_LAYER)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
    
     * Requests that the local endpoint set the specified Adaptation Layer
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * Indication parameter for all future INIT and INIT-ACK exchanges.
     */
    
    static int sctp_getsockopt_adaptation_layer(struct sock *sk, int len,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				  char __user *optval, int __user *optlen)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (len != sizeof(struct sctp_setadaptation))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	adaptation.ssb_adaptation_ind = sctp_sk(sk)->adaptation_ind;
    	if (copy_to_user(optval, &adaptation, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    /*
     *
     * 7.1.14 Set default send parameters (SCTP_DEFAULT_SEND_PARAM)
     *
     *   Applications that wish to use the sendto() system call may wish to
     *   specify a default set of parameters that would normally be supplied
     *   through the inclusion of ancillary data.  This socket option allows
     *   such an application to set the default sctp_sndrcvinfo structure.
    
    
     *   The application that wishes to use this socket option simply passes
     *   in to this call the sctp_sndrcvinfo structure defined in Section
     *   5.2.2) The input parameters accepted by this call include
     *   sinfo_stream, sinfo_flags, sinfo_ppid, sinfo_context,
     *   sinfo_timetolive.  The user must provide the sinfo_assoc_id field in
     *   to this call if the caller is using the UDP model.
     *
     *   For getsockopt, it get the default sctp_sndrcvinfo structure.
     */
    static int sctp_getsockopt_default_send_param(struct sock *sk,
    					int len, char __user *optval,
    					int __user *optlen)
    {
    	struct sctp_sndrcvinfo info;
    	struct sctp_association *asoc;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (len != sizeof(struct sctp_sndrcvinfo))
    		return -EINVAL;
    	if (copy_from_user(&info, optval, sizeof(struct sctp_sndrcvinfo)))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, info.sinfo_assoc_id);
    	if (!asoc && info.sinfo_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc) {
    		info.sinfo_stream = asoc->default_stream;
    		info.sinfo_flags = asoc->default_flags;
    		info.sinfo_ppid = asoc->default_ppid;
    		info.sinfo_context = asoc->default_context;
    		info.sinfo_timetolive = asoc->default_timetolive;
    	} else {
    		info.sinfo_stream = sp->default_stream;
    		info.sinfo_flags = sp->default_flags;
    		info.sinfo_ppid = sp->default_ppid;
    		info.sinfo_context = sp->default_context;
    		info.sinfo_timetolive = sp->default_timetolive;
    	}
    
    	if (copy_to_user(optval, &info, sizeof(struct sctp_sndrcvinfo)))
    		return -EFAULT;
    
    	return 0;
    }
    
    /*
     *
     * 7.1.5 SCTP_NODELAY
     *
     * Turn on/off any Nagle-like algorithm.  This means that packets are
     * generally sent as soon as possible and no unnecessary delays are
     * introduced, at the cost of more packets in the network.  Expects an
     * integer boolean flag.
     */
    
    static int sctp_getsockopt_nodelay(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)->nodelay == 1);
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    	return 0;
    }
    
    /*
     *
     * 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))
    		return -EINVAL;
    
    	if (copy_from_user(&rtoinfo, optval, sizeof (struct sctp_rtoinfo)))
    		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))
    		return -EINVAL;
    
    	if (copy_from_user(&assocparams, optval,
    			sizeof (struct sctp_assocparams)))
    		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))
    		return -EINVAL;
    
    	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
    /*
     * 7.1.17 Set the maximum fragrmentation size (SCTP_MAXSEG)
     *
     * This socket option specifies the maximum size to put in any outgoing
     * SCTP chunk.  If a message is larger than this size it will be
     * 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.
     */
    static int sctp_getsockopt_maxseg(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)->user_frag;
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    /*
     * 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)
    {
            u32 val;
    
    	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)
    {
            int val;
    
    	if (len < sizeof(int))
    		return -EINVAL;
    
    	len = sizeof(int);
    
    	val = sctp_sk(sk)->max_burst;
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    
    	return -ENOTSUPP;
    }
    
    
    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_TIME:
    		retval = sctp_getsockopt_delayed_ack_time(sk, len, optval,
    							  optlen);
    		break;
    
    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_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;
    
    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 */
    	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.
    		 *
    		 * 'sctp_port_rover' was the last port assigned, so
    		 * we start to search from 'sctp_port_rover +
    		 * 1'. What we do is first check if port 'rover' is
    		 * already in the hash table; if not, we use that; if
    		 * it is, we try next.
    		 */
    		int low = sysctl_local_port_range[0];
    		int high = sysctl_local_port_range[1];
    		int remaining = (high - low) + 1;
    		int rover;
    		int index;
    
    		sctp_spin_lock(&sctp_port_alloc_lock);
    		rover = sctp_port_rover;
    		do {
    			rover++;
    			if ((rover < low) || (rover > high))
    				rover = low;
    			index = sctp_phashfn(rover);
    			head = &sctp_port_hashtable[index];
    			sctp_spin_lock(&head->lock);
    			for (pp = head->chain; pp; pp = pp->next)
    				if (pp->port == rover)
    					goto next;
    			break;
    		next:
    			sctp_spin_unlock(&head->lock);
    		} while (--remaining > 0);
    		sctp_port_rover = rover;
    		sctp_spin_unlock(&sctp_port_alloc_lock);
    
    		/* Exhausted local port range during search? */
    		ret = 1;
    		if (remaining <= 0)