Skip to content
Snippets Groups Projects
socket.c 189 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * This socket option is applicable to the UDP-style socket only.  When
     * set it will cause associations that are idle for more than the
     * specified number of seconds to automatically close.  An association
     * being idle is defined an association that has NOT sent or received
     * user data.  The special value of '0' indicates that no automatic
     * close of any associations should be performed.  The option expects an
     * integer defining the number of seconds of idle time before an
     * association is closed.
     */
    static int sctp_getsockopt_autoclose(struct sock *sk, int len, char __user *optval, int __user *optlen)
    {
    	/* Applicable to UDP-style socket only */
    	if (sctp_style(sk, TCP))
    		return -EOPNOTSUPP;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	len = sizeof(int);
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &sctp_sk(sk)->autoclose, sizeof(int)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    	return 0;
    }
    
    /* Helper routine to branch off an association to a new socket.  */
    SCTP_STATIC int sctp_do_peeloff(struct sctp_association *asoc,
    				struct socket **sockp)
    {
    	struct sock *sk = asoc->base.sk;
    	struct socket *sock;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int err = 0;
    
    	/* An association cannot be branched off from an already peeled-off
    	 * socket, nor is this supported for tcp style sockets.
    	 */
    	if (!sctp_style(sk, UDP))
    		return -EINVAL;
    
    	/* Create a new socket.  */
    	err = sock_create(sk->sk_family, SOCK_SEQPACKET, IPPROTO_SCTP, &sock);
    	if (err < 0)
    		return err;
    
    
    	sctp_copy_sock(sock->sk, sk, asoc);
    
    
    	/* Make peeled-off sockets more like 1-1 accepted sockets.
    	 * Set the daddr and initialize id to something more random
    	 */
    
    	af = sctp_get_af_specific(asoc->peer.primary_addr.sa.sa_family);
    	af->to_sk_daddr(&asoc->peer.primary_addr, sk);
    
    
    	/* Populate the fields of the newsk from the oldsk and migrate the
    	 * asoc to the newsk.
    	 */
    	sctp_sock_migrate(sk, sock->sk, asoc, SCTP_SOCKET_UDP_HIGH_BANDWIDTH);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	*sockp = sock;
    
    	return err;
    }
    
    static int sctp_getsockopt_peeloff(struct sock *sk, int len, char __user *optval, int __user *optlen)
    {
    	sctp_peeloff_arg_t peeloff;
    	struct socket *newsock;
    	int retval = 0;
    	struct sctp_association *asoc;
    
    
    	if (len < sizeof(sctp_peeloff_arg_t))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	len = sizeof(sctp_peeloff_arg_t);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (copy_from_user(&peeloff, optval, len))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, peeloff.associd);
    	if (!asoc) {
    		retval = -EINVAL;
    		goto out;
    	}
    
    
    	SCTP_DEBUG_PRINTK("%s: sk: %p asoc: %p\n", __func__, sk, asoc);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	retval = sctp_do_peeloff(asoc, &newsock);
    	if (retval < 0)
    		goto out;
    
    	/* Map the socket to an unused fd that can be returned to the user.  */
    
    	retval = sock_map_fd(newsock, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (retval < 0) {
    		sock_release(newsock);
    		goto out;
    	}
    
    	SCTP_DEBUG_PRINTK("%s: sk: %p asoc: %p newsk: %p sd: %d\n",
    
    			  __func__, sk, asoc, newsock->sk, retval);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Return the fd mapped to the new socket.  */
    	peeloff.sd = retval;
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (copy_to_user(optval, &peeloff, len))
    		retval = -EFAULT;
    
    out:
    	return retval;
    }
    
    /* 7.1.13 Peer Address Parameters (SCTP_PEER_ADDR_PARAMS)
     *
     * Applications can enable or disable heartbeats for any peer address of
     * an association, modify an address's heartbeat interval, force a
     * heartbeat to be sent immediately, and adjust the address's maximum
     * number of retransmissions sent before an address is considered
     * unreachable.  The following structure is used to access and modify an
     * address's parameters:
     *
     *  struct sctp_paddrparams {
    
     *     sctp_assoc_t            spp_assoc_id;
     *     struct sockaddr_storage spp_address;
     *     uint32_t                spp_hbinterval;
     *     uint16_t                spp_pathmaxrxt;
     *     uint32_t                spp_pathmtu;
     *     uint32_t                spp_sackdelay;
     *     uint32_t                spp_flags;
     * };
     *
     *   spp_assoc_id    - (one-to-many style socket) This is filled in the
     *                     application, and identifies the association for
     *                     this query.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *   spp_address     - This specifies which address is of interest.
     *   spp_hbinterval  - This contains the value of the heartbeat interval,
    
     *                     in milliseconds.  If a  value of zero
     *                     is present in this field then no changes are to
     *                     be made to this parameter.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *   spp_pathmaxrxt  - This contains the maximum number of
     *                     retransmissions before this address shall be
    
     *                     considered unreachable. If a  value of zero
     *                     is present in this field then no changes are to
     *                     be made to this parameter.
     *   spp_pathmtu     - When Path MTU discovery is disabled the value
     *                     specified here will be the "fixed" path mtu.
     *                     Note that if the spp_address field is empty
     *                     then all associations on this address will
     *                     have this fixed path mtu set upon them.
     *
     *   spp_sackdelay   - When delayed sack is enabled, this value specifies
     *                     the number of milliseconds that sacks will be delayed
     *                     for. This value will apply to all addresses of an
     *                     association if the spp_address field is empty. Note
     *                     also, that if delayed sack is enabled and this
     *                     value is set to 0, no change is made to the last
     *                     recorded delayed sack timer value.
     *
     *   spp_flags       - These flags are used to control various features
     *                     on an association. The flag field may contain
     *                     zero or more of the following options.
     *
     *                     SPP_HB_ENABLE  - Enable heartbeats on the
     *                     specified address. Note that if the address
     *                     field is empty all addresses for the association
     *                     have heartbeats enabled upon them.
     *
     *                     SPP_HB_DISABLE - Disable heartbeats on the
     *                     speicifed address. Note that if the address
     *                     field is empty all addresses for the association
     *                     will have their heartbeats disabled. Note also
     *                     that SPP_HB_ENABLE and SPP_HB_DISABLE are
     *                     mutually exclusive, only one of these two should
     *                     be specified. Enabling both fields will have
     *                     undetermined results.
     *
     *                     SPP_HB_DEMAND - Request a user initiated heartbeat
     *                     to be made immediately.
     *
     *                     SPP_PMTUD_ENABLE - This field will enable PMTU
     *                     discovery upon the specified address. Note that
     *                     if the address feild is empty then all addresses
     *                     on the association are effected.
     *
     *                     SPP_PMTUD_DISABLE - This field will disable PMTU
     *                     discovery upon the specified address. Note that
     *                     if the address feild is empty then all addresses
     *                     on the association are effected. Not also that
     *                     SPP_PMTUD_ENABLE and SPP_PMTUD_DISABLE are mutually
     *                     exclusive. Enabling both will have undetermined
     *                     results.
     *
     *                     SPP_SACKDELAY_ENABLE - Setting this flag turns
     *                     on delayed sack. The time specified in spp_sackdelay
     *                     is used to specify the sack delay for this address. Note
     *                     that if spp_address is empty then all addresses will
     *                     enable delayed sack and take on the sack delay
     *                     value specified in spp_sackdelay.
     *                     SPP_SACKDELAY_DISABLE - Setting this flag turns
     *                     off delayed sack. If the spp_address field is blank then
     *                     delayed sack is disabled for the entire association. Note
     *                     also that this field is mutually exclusive to
     *                     SPP_SACKDELAY_ENABLE, setting both will have undefined
     *                     results.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    static int sctp_getsockopt_peer_addr_params(struct sock *sk, int len,
    
    					    char __user *optval, int __user *optlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct sctp_paddrparams  params;
    	struct sctp_transport   *trans = NULL;
    	struct sctp_association *asoc = NULL;
    	struct sctp_sock        *sp = sctp_sk(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (len < sizeof(struct sctp_paddrparams))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	len = sizeof(struct sctp_paddrparams);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (copy_from_user(&params, optval, len))
    		return -EFAULT;
    
    
    	/* If an address other than INADDR_ANY is specified, and
    	 * no transport is found, then the request is invalid.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    
    	if (!sctp_is_any(sk, ( union sctp_addr *)&params.spp_address)) {
    
    		trans = sctp_addr_id2transport(sk, &params.spp_address,
    					       params.spp_assoc_id);
    		if (!trans) {
    			SCTP_DEBUG_PRINTK("Failed no transport\n");
    			return -EINVAL;
    		}
    
    	/* Get association, if assoc_id != 0 and the socket is a one
    	 * to many style socket, and an association was not found, then
    	 * the id was invalid.
    	 */
    	asoc = sctp_id2assoc(sk, params.spp_assoc_id);
    	if (!asoc && params.spp_assoc_id && sctp_style(sk, UDP)) {
    		SCTP_DEBUG_PRINTK("Failed no association\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (trans) {
    		/* Fetch transport values. */
    		params.spp_hbinterval = jiffies_to_msecs(trans->hbinterval);
    		params.spp_pathmtu    = trans->pathmtu;
    		params.spp_pathmaxrxt = trans->pathmaxrxt;
    		params.spp_sackdelay  = jiffies_to_msecs(trans->sackdelay);
    
    		/*draft-11 doesn't say what to return in spp_flags*/
    		params.spp_flags      = trans->param_flags;
    	} else if (asoc) {
    		/* Fetch association values. */
    		params.spp_hbinterval = jiffies_to_msecs(asoc->hbinterval);
    		params.spp_pathmtu    = asoc->pathmtu;
    		params.spp_pathmaxrxt = asoc->pathmaxrxt;
    		params.spp_sackdelay  = jiffies_to_msecs(asoc->sackdelay);
    
    		/*draft-11 doesn't say what to return in spp_flags*/
    		params.spp_flags      = asoc->param_flags;
    	} else {
    		/* Fetch socket values. */
    		params.spp_hbinterval = sp->hbinterval;
    		params.spp_pathmtu    = sp->pathmtu;
    		params.spp_sackdelay  = sp->sackdelay;
    		params.spp_pathmaxrxt = sp->pathmaxrxt;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		/*draft-11 doesn't say what to return in spp_flags*/
    		params.spp_flags      = sp->param_flags;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (copy_to_user(optval, &params, len))
    		return -EFAULT;
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    /*
     * 7.1.23.  Get or set delayed ack timer (SCTP_DELAYED_SACK)
     *
     * This option will effect the way delayed acks are performed.  This
     * option allows you to get or set the delayed ack time, in
     * milliseconds.  It also allows changing the delayed ack frequency.
     * Changing the frequency to 1 disables the delayed sack algorithm.  If
     * the assoc_id is 0, then this sets or gets the endpoints default
     * values.  If the assoc_id field is non-zero, then the set or get
     * effects the specified association for the one to many model (the
     * assoc_id field is ignored by the one to one model).  Note that if
     * sack_delay or sack_freq are 0 when setting this option, then the
     * current values will remain unchanged.
     *
     * struct sctp_sack_info {
     *     sctp_assoc_t            sack_assoc_id;
     *     uint32_t                sack_delay;
     *     uint32_t                sack_freq;
     * };
    
     * sack_assoc_id -  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).
    
     * sack_delay -  This parameter contains the number of milliseconds that
     *    the user is requesting the delayed ACK timer be set to.  Note that
     *    this value is defined in the standard to be between 200 and 500
     *    milliseconds.
    
     * sack_freq -  This parameter contains the number of packets that must
     *    be received before a sack is sent without waiting for the delay
     *    timer to expire.  The default value for this is 2, setting this
     *    value to 1 will disable the delayed sack algorithm.
    
    static int sctp_getsockopt_delayed_ack(struct sock *sk, int len,
    
    					    char __user *optval,
    					    int __user *optlen)
    {
    
    	struct sctp_sack_info    params;
    
    	struct sctp_association *asoc = NULL;
    	struct sctp_sock        *sp = sctp_sk(sk);
    
    
    	if (len >= sizeof(struct sctp_sack_info)) {
    		len = sizeof(struct sctp_sack_info);
    
    		if (copy_from_user(&params, optval, len))
    			return -EFAULT;
    	} else if (len == sizeof(struct sctp_assoc_value)) {
    
    		pr_warn("Use of struct sctp_assoc_value in delayed_ack socket option deprecated\n");
    		pr_warn("Use struct sctp_sack_info instead\n");
    
    		if (copy_from_user(&params, optval, len))
    			return -EFAULT;
    	} else
    		return - EINVAL;
    
    	/* Get association, if sack_assoc_id != 0 and the socket is a one
    
    	 * to many style socket, and an association was not found, then
    	 * the id was invalid.
    
    	asoc = sctp_id2assoc(sk, params.sack_assoc_id);
    	if (!asoc && params.sack_assoc_id && sctp_style(sk, UDP))
    
    		return -EINVAL;
    
    	if (asoc) {
    		/* Fetch association values. */
    
    		if (asoc->param_flags & SPP_SACKDELAY_ENABLE) {
    			params.sack_delay = jiffies_to_msecs(
    
    			params.sack_freq = asoc->sackfreq;
    
    		} else {
    			params.sack_delay = 0;
    			params.sack_freq = 1;
    		}
    
    	} else {
    		/* Fetch socket values. */
    
    		if (sp->param_flags & SPP_SACKDELAY_ENABLE) {
    			params.sack_delay  = sp->sackdelay;
    			params.sack_freq = sp->sackfreq;
    		} else {
    			params.sack_delay  = 0;
    			params.sack_freq = 1;
    		}
    
    	}
    
    	if (copy_to_user(optval, &params, len))
    		return -EFAULT;
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* 7.1.3 Initialization Parameters (SCTP_INITMSG)
     *
     * Applications can specify protocol parameters for the default association
     * initialization.  The option name argument to setsockopt() and getsockopt()
     * is SCTP_INITMSG.
     *
     * Setting initialization parameters is effective only on an unconnected
     * socket (for UDP-style sockets only future associations are effected
     * by the change).  With TCP-style sockets, this option is inherited by
     * sockets derived from a listener socket.
     */
    static int sctp_getsockopt_initmsg(struct sock *sk, int len, char __user *optval, int __user *optlen)
    {
    
    	if (len < sizeof(struct sctp_initmsg))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	len = sizeof(struct sctp_initmsg);
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (copy_to_user(optval, &sctp_sk(sk)->initmsg, len))
    		return -EFAULT;
    	return 0;
    }
    
    
    
    static int sctp_getsockopt_peer_addrs(struct sock *sk, int len,
    				      char __user *optval, int __user *optlen)
    {
    	struct sctp_association *asoc;
    	int cnt = 0;
    	struct sctp_getaddrs getaddrs;
    	struct sctp_transport *from;
    	void __user *to;
    	union sctp_addr temp;
    	struct sctp_sock *sp = sctp_sk(sk);
    	int addrlen;
    	size_t space_left;
    	int bytes_copied;
    
    	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.  */
    	asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
    	if (!asoc)
    		return -EINVAL;
    
    	to = optval + offsetof(struct sctp_getaddrs,addrs);
    
    	space_left = len - offsetof(struct sctp_getaddrs,addrs);
    
    	list_for_each_entry(from, &asoc->peer.transport_addr_list,
    				transports) {
    
    		memcpy(&temp, &from->ipaddr, sizeof(temp));
    
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp, &temp);
    
    		addrlen = sctp_get_af_specific(temp.sa.sa_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_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;
    
    
    	rcu_read_lock();
    	list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) {
    		if (!addr->valid)
    			continue;
    
    
    		if ((PF_INET == sk->sk_family) &&
    
    		    (AF_INET6 == addr->a.sa.sa_family))
    
    		if ((PF_INET6 == sk->sk_family) &&
    		    inet_v6_ipv6only(sk) &&
    		    (AF_INET == addr->a.sa.sa_family))
    			continue;
    
    		memcpy(&temp, &addr->a, sizeof(temp));
    
    		if (!temp.v4.sin_port)
    			temp.v4.sin_port = htons(port);
    
    
    		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
    								&temp);
    		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
    
    		if (space_left < addrlen) {
    			cnt =  -ENOMEM;
    			break;
    		}
    
    		*bytes_copied += addrlen;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    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;
    	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;
    	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;
    	} else {
    		asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		bp = &asoc->base.bind_addr;
    	}
    
    	to = optval + offsetof(struct sctp_getaddrs,addrs);
    
    	space_left = len - offsetof(struct sctp_getaddrs,addrs);
    
    
    	addrs = kmalloc(space_left, GFP_KERNEL);
    	if (!addrs)
    		return -ENOMEM;
    
    
    	/* 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);
    
    			cnt = sctp_copy_laddrs(sk, bp->port, addrs,
    						space_left, &bytes_copied);
    
    			goto copy_getaddrs;
    
    	/* Protection on the bound address list is not needed since
    	 * in the socket option context we hold a socket lock and
    	 * thus the bound address list can't change.
    	 */
    	list_for_each_entry(addr, &bp->address_list, 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?*/
    
    		memcpy(buf, &temp, addrlen);
    		buf += addrlen;
    
    	if (copy_to_user(to, addrs, bytes_copied)) {
    		err = -EFAULT;
    
    		goto out;
    
    	if (put_user(cnt, &((struct sctp_getaddrs __user *)optval)->addr_num)) {
    		err = -EFAULT;
    
    		goto out;
    
    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))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	len = sizeof(struct sctp_prim);
    
    	if (copy_from_user(&prim, optval, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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 (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &prim, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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;
    
    
    	len = sizeof(struct sctp_setadaptation);
    
    
    	adaptation.ssb_adaptation_ind = sctp_sk(sk)->adaptation_ind;
    
    
    	if (put_user(len, optlen))
    		return -EFAULT;
    
    	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))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	len = sizeof(struct sctp_sndrcvinfo);
    
    	if (copy_from_user(&info, optval, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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 (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &info, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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))
    
    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
    
    
    		pr_warn("Use of int in maxseg socket option deprecated\n");
    		pr_warn("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;
    	}