Skip to content
Snippets Groups Projects
socket.c 175 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /* 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_HB_TIME_IS_ZERO - Specify's that the time for
     *                     heartbeat delayis to be set to the value of 0
     *                     milliseconds.
     *
    
     *                     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
     */
    
    Adrian Bunk's avatar
    Adrian Bunk committed
    static int sctp_apply_peer_addr_params(struct sctp_paddrparams *params,
    				       struct sctp_transport   *trans,
    				       struct sctp_association *asoc,
    				       struct sctp_sock        *sp,
    				       int                      hb_change,
    				       int                      pmtud_change,
    				       int                      sackdelay_change)
    
    {
    	int error;
    
    	if (params->spp_flags & SPP_HB_DEMAND && trans) {
    		error = sctp_primitive_REQUESTHEARTBEAT (trans->asoc, trans);
    		if (error)
    			return error;
    	}
    
    
    	/* Note that unless the spp_flag is set to SPP_HB_ENABLE the value of
    	 * this field is ignored.  Note also that a value of zero indicates
    	 * the current setting should be left unchanged.
    	 */
    	if (params->spp_flags & SPP_HB_ENABLE) {
    
    		/* Re-zero the interval if the SPP_HB_TIME_IS_ZERO is
    		 * set.  This lets us use 0 value when this flag
    		 * is set.
    		 */
    		if (params->spp_flags & SPP_HB_TIME_IS_ZERO)
    			params->spp_hbinterval = 0;
    
    		if (params->spp_hbinterval ||
    		    (params->spp_flags & SPP_HB_TIME_IS_ZERO)) {
    			if (trans) {
    				trans->hbinterval =
    				    msecs_to_jiffies(params->spp_hbinterval);
    			} else if (asoc) {
    				asoc->hbinterval =
    				    msecs_to_jiffies(params->spp_hbinterval);
    			} else {
    				sp->hbinterval = params->spp_hbinterval;
    			}
    
    		}
    	}
    
    	if (hb_change) {
    		if (trans) {
    			trans->param_flags =
    				(trans->param_flags & ~SPP_HB) | hb_change;
    		} else if (asoc) {
    			asoc->param_flags =
    				(asoc->param_flags & ~SPP_HB) | hb_change;
    		} else {
    			sp->param_flags =
    				(sp->param_flags & ~SPP_HB) | hb_change;
    		}
    	}
    
    
    	/* When Path MTU discovery is disabled the value specified here will
    	 * be the "fixed" path mtu (i.e. the value of the spp_flags field must
    	 * include the flag SPP_PMTUD_DISABLE for this field to have any
    	 * effect).
    	 */
    	if ((params->spp_flags & SPP_PMTUD_DISABLE) && params->spp_pathmtu) {
    
    		if (trans) {
    			trans->pathmtu = params->spp_pathmtu;
    			sctp_assoc_sync_pmtu(asoc);
    		} else if (asoc) {
    			asoc->pathmtu = params->spp_pathmtu;
    			sctp_frag_point(sp, params->spp_pathmtu);
    		} else {
    			sp->pathmtu = params->spp_pathmtu;
    		}
    	}
    
    	if (pmtud_change) {
    		if (trans) {
    			int update = (trans->param_flags & SPP_PMTUD_DISABLE) &&
    				(params->spp_flags & SPP_PMTUD_ENABLE);
    			trans->param_flags =
    				(trans->param_flags & ~SPP_PMTUD) | pmtud_change;
    			if (update) {
    				sctp_transport_pmtu(trans);
    				sctp_assoc_sync_pmtu(asoc);
    			}
    		} else if (asoc) {
    			asoc->param_flags =
    				(asoc->param_flags & ~SPP_PMTUD) | pmtud_change;
    		} else {
    			sp->param_flags =
    				(sp->param_flags & ~SPP_PMTUD) | pmtud_change;
    		}
    	}
    
    
    	/* Note that unless the spp_flag is set to SPP_SACKDELAY_ENABLE the
    	 * value of this field is ignored.  Note also that a value of zero
    	 * indicates the current setting should be left unchanged.
    	 */
    	if ((params->spp_flags & SPP_SACKDELAY_ENABLE) && params->spp_sackdelay) {
    
    		if (trans) {
    			trans->sackdelay =
    				msecs_to_jiffies(params->spp_sackdelay);
    		} else if (asoc) {
    			asoc->sackdelay =
    				msecs_to_jiffies(params->spp_sackdelay);
    		} else {
    			sp->sackdelay = params->spp_sackdelay;
    		}
    	}
    
    	if (sackdelay_change) {
    		if (trans) {
    			trans->param_flags =
    				(trans->param_flags & ~SPP_SACKDELAY) |
    				sackdelay_change;
    		} else if (asoc) {
    			asoc->param_flags =
    				(asoc->param_flags & ~SPP_SACKDELAY) |
    				sackdelay_change;
    		} else {
    			sp->param_flags =
    				(sp->param_flags & ~SPP_SACKDELAY) |
    				sackdelay_change;
    		}
    	}
    
    
    	/* Note that unless the spp_flag is set to SPP_PMTUD_ENABLE the value
    	 * of this field is ignored.  Note also that a value of zero
    	 * indicates the current setting should be left unchanged.
    	 */
    	if ((params->spp_flags & SPP_PMTUD_ENABLE) && params->spp_pathmaxrxt) {
    
    		if (trans) {
    			trans->pathmaxrxt = params->spp_pathmaxrxt;
    		} else if (asoc) {
    			asoc->pathmaxrxt = params->spp_pathmaxrxt;
    		} else {
    			sp->pathmaxrxt = params->spp_pathmaxrxt;
    		}
    	}
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int sctp_setsockopt_peer_addr_params(struct sock *sk,
    					    char __user *optval, int optlen)
    {
    
    	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
    	int error;
    
    	int hb_change, pmtud_change, sackdelay_change;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (optlen != sizeof(struct sctp_paddrparams))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (copy_from_user(&params, optval, optlen))
    		return -EFAULT;
    
    
    	/* Validate flags and value parameters. */
    	hb_change        = params.spp_flags & SPP_HB;
    	pmtud_change     = params.spp_flags & SPP_PMTUD;
    	sackdelay_change = params.spp_flags & SPP_SACKDELAY;
    
    	if (hb_change        == SPP_HB ||
    	    pmtud_change     == SPP_PMTUD ||
    	    sackdelay_change == SPP_SACKDELAY ||
    	    params.spp_sackdelay > 500 ||
    	    (params.spp_pathmtu
    	    && params.spp_pathmtu < SCTP_DEFAULT_MINSEGMENT))
    		return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* If an address other than INADDR_ANY is specified, and
    	 * no transport is found, then the request is invalid.
    	 */
    	if (!sctp_is_any(( union sctp_addr *)&params.spp_address)) {
    		trans = sctp_addr_id2transport(sk, &params.spp_address,
    					       params.spp_assoc_id);
    		if (!trans)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			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))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    
    	/* Heartbeat demand can only be sent on a transport or
    	 * association, but not a socket.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    
    	if (params.spp_flags & SPP_HB_DEMAND && !trans && !asoc)
    		return -EINVAL;
    
    	/* Process parameters. */
    	error = sctp_apply_peer_addr_params(&params, trans, asoc, sp,
    					    hb_change, pmtud_change,
    					    sackdelay_change);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (error)
    		return error;
    
    	/* If changes are for association, also apply parameters to each
    	 * transport.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    
    	if (!trans && asoc) {
    		struct list_head *pos;
    
    		list_for_each(pos, &asoc->peer.transport_addr_list) {
    			trans = list_entry(pos, struct sctp_transport,
    					   transports);
    			sctp_apply_peer_addr_params(&params, trans, asoc, sp,
    						    hb_change, pmtud_change,
    						    sackdelay_change);
    		}
    	}
    
    /* 7.1.23. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME)
    
     *
     *   This options will get or set the delayed ack timer.  The time is set
     *   in milliseconds.  If the assoc_id is 0, then this sets or gets the
     *   endpoints default delayed ack timer value.  If the assoc_id field is
     *   non-zero, then the set or get effects the specified association.
     *
     *   struct sctp_assoc_value {
     *       sctp_assoc_t            assoc_id;
     *       uint32_t                assoc_value;
     *   };
     *
     *     assoc_id    - This parameter, indicates which association the
     *                   user is preforming 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 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.
     *
     *                   Note: a value of zero will leave the value alone,
     *                   but disable SACK delay. A non-zero value will also
     *                   enable SACK delay.
     */
    
    static int sctp_setsockopt_delayed_ack_time(struct sock *sk,
    					    char __user *optval, int optlen)
    {
    	struct sctp_assoc_value  params;
    	struct sctp_transport   *trans = NULL;
    	struct sctp_association *asoc = NULL;
    	struct sctp_sock        *sp = sctp_sk(sk);
    
    	if (optlen != sizeof(struct sctp_assoc_value))
    		return - EINVAL;
    
    	if (copy_from_user(&params, optval, optlen))
    		return -EFAULT;
    
    	/* Validate value parameter. */
    	if (params.assoc_value > 500)
    		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.assoc_id);
    	if (!asoc && params.assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (params.assoc_value) {
    		if (asoc) {
    			asoc->sackdelay =
    				msecs_to_jiffies(params.assoc_value);
    
    			asoc->param_flags =
    
    				(asoc->param_flags & ~SPP_SACKDELAY) |
    				SPP_SACKDELAY_ENABLE;
    		} else {
    			sp->sackdelay = params.assoc_value;
    
    			sp->param_flags =
    
    				(sp->param_flags & ~SPP_SACKDELAY) |
    				SPP_SACKDELAY_ENABLE;
    		}
    	} else {
    		if (asoc) {
    
    			asoc->param_flags =
    
    				(asoc->param_flags & ~SPP_SACKDELAY) |
    				SPP_SACKDELAY_DISABLE;
    		} else {
    
    			sp->param_flags =
    
    				(sp->param_flags & ~SPP_SACKDELAY) |
    				SPP_SACKDELAY_DISABLE;
    		}
    	}
    
    	/* If change is for association, also apply to each transport. */
    	if (asoc) {
    		struct list_head *pos;
    
    		list_for_each(pos, &asoc->peer.transport_addr_list) {
    			trans = list_entry(pos, struct sctp_transport,
    					   transports);
    			if (params.assoc_value) {
    				trans->sackdelay =
    					msecs_to_jiffies(params.assoc_value);
    
    				trans->param_flags =
    
    					(trans->param_flags & ~SPP_SACKDELAY) |
    					SPP_SACKDELAY_ENABLE;
    			} else {
    
    				trans->param_flags =
    
    					(trans->param_flags & ~SPP_SACKDELAY) |
    					SPP_SACKDELAY_DISABLE;
    			}
    		}
    	}
    
    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_setsockopt_initmsg(struct sock *sk, char __user *optval, int optlen)
    {
    	struct sctp_initmsg sinit;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (optlen != sizeof(struct sctp_initmsg))
    		return -EINVAL;
    	if (copy_from_user(&sinit, optval, optlen))
    		return -EFAULT;
    
    	if (sinit.sinit_num_ostreams)
    
    		sp->initmsg.sinit_num_ostreams = sinit.sinit_num_ostreams;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (sinit.sinit_max_instreams)
    
    		sp->initmsg.sinit_max_instreams = sinit.sinit_max_instreams;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (sinit.sinit_max_attempts)
    
    		sp->initmsg.sinit_max_attempts = sinit.sinit_max_attempts;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (sinit.sinit_max_init_timeo)
    
    		sp->initmsg.sinit_max_init_timeo = sinit.sinit_max_init_timeo;
    
    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.
     */
    static int sctp_setsockopt_default_send_param(struct sock *sk,
    						char __user *optval, int optlen)
    {
    	struct sctp_sndrcvinfo info;
    	struct sctp_association *asoc;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (optlen != sizeof(struct sctp_sndrcvinfo))
    		return -EINVAL;
    	if (copy_from_user(&info, optval, optlen))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, info.sinfo_assoc_id);
    	if (!asoc && info.sinfo_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc) {
    		asoc->default_stream = info.sinfo_stream;
    		asoc->default_flags = info.sinfo_flags;
    		asoc->default_ppid = info.sinfo_ppid;
    		asoc->default_context = info.sinfo_context;
    		asoc->default_timetolive = info.sinfo_timetolive;
    	} else {
    		sp->default_stream = info.sinfo_stream;
    		sp->default_flags = info.sinfo_flags;
    		sp->default_ppid = info.sinfo_ppid;
    		sp->default_context = info.sinfo_context;
    		sp->default_timetolive = info.sinfo_timetolive;
    	}
    
    	return 0;
    }
    
    /* 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_setsockopt_primary_addr(struct sock *sk, char __user *optval,
    					int optlen)
    {
    	struct sctp_prim prim;
    	struct sctp_transport *trans;
    
    	if (optlen != sizeof(struct sctp_prim))
    		return -EINVAL;
    
    	if (copy_from_user(&prim, optval, sizeof(struct sctp_prim)))
    		return -EFAULT;
    
    	trans = sctp_addr_id2transport(sk, &prim.ssp_addr, prim.ssp_assoc_id);
    	if (!trans)
    		return -EINVAL;
    
    	sctp_assoc_set_primary(trans->asoc, trans);
    
    	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_setsockopt_nodelay(struct sock *sk, char __user *optval,
    					int optlen)
    {
    	int val;
    
    	if (optlen < sizeof(int))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    
    	sctp_sk(sk)->nodelay = (val == 0) ? 0 : 1;
    	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_setsockopt_rtoinfo(struct sock *sk, char __user *optval, int optlen) {
    	struct sctp_rtoinfo rtoinfo;
    	struct sctp_association *asoc;
    
    	if (optlen != sizeof (struct sctp_rtoinfo))
    		return -EINVAL;
    
    	if (copy_from_user(&rtoinfo, optval, optlen))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, rtoinfo.srto_assoc_id);
    
    	/* Set the values to the specific association */
    	if (!asoc && rtoinfo.srto_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	if (asoc) {
    		if (rtoinfo.srto_initial != 0)
    
    			asoc->rto_initial =
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				msecs_to_jiffies(rtoinfo.srto_initial);
    		if (rtoinfo.srto_max != 0)
    			asoc->rto_max = msecs_to_jiffies(rtoinfo.srto_max);
    		if (rtoinfo.srto_min != 0)
    			asoc->rto_min = msecs_to_jiffies(rtoinfo.srto_min);
    	} else {
    		/* If there is no association or the association-id = 0
    		 * set the values to the endpoint.
    		 */
    		struct sctp_sock *sp = sctp_sk(sk);
    
    		if (rtoinfo.srto_initial != 0)
    			sp->rtoinfo.srto_initial = rtoinfo.srto_initial;
    		if (rtoinfo.srto_max != 0)
    			sp->rtoinfo.srto_max = rtoinfo.srto_max;
    		if (rtoinfo.srto_min != 0)
    			sp->rtoinfo.srto_min = rtoinfo.srto_min;
    	}
    
    	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_setsockopt_associnfo(struct sock *sk, char __user *optval, int optlen)
    {
    
    	struct sctp_assocparams assocparams;
    	struct sctp_association *asoc;
    
    	if (optlen != sizeof(struct sctp_assocparams))
    		return -EINVAL;
    	if (copy_from_user(&assocparams, optval, optlen))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, assocparams.sasoc_assoc_id);
    
    	if (!asoc && assocparams.sasoc_assoc_id && sctp_style(sk, UDP))
    		return -EINVAL;
    
    	/* Set the values to the specific association */
    	if (asoc) {
    
    		if (assocparams.sasoc_asocmaxrxt != 0) {
    			__u32 path_sum = 0;
    			int   paths = 0;
    			struct list_head *pos;
    			struct sctp_transport *peer_addr;
    
    			list_for_each(pos, &asoc->peer.transport_addr_list) {
    				peer_addr = list_entry(pos,
    						struct sctp_transport,
    						transports);
    				path_sum += peer_addr->pathmaxrxt;
    				paths++;
    			}
    
    			/* Only validate asocmaxrxt if we have more then
    			 * one path/transport.  We do this because path
    			 * retransmissions are only counted when we have more
    			 * then one path.
    			 */
    			if (paths > 1 &&
    			    assocparams.sasoc_asocmaxrxt > path_sum)
    				return -EINVAL;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			asoc->max_retrans = assocparams.sasoc_asocmaxrxt;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (assocparams.sasoc_cookie_life != 0) {
    			asoc->cookie_life.tv_sec =
    					assocparams.sasoc_cookie_life / 1000;
    			asoc->cookie_life.tv_usec =
    					(assocparams.sasoc_cookie_life % 1000)
    					* 1000;
    		}
    	} else {
    		/* Set the values to the endpoint */
    		struct sctp_sock *sp = sctp_sk(sk);
    
    		if (assocparams.sasoc_asocmaxrxt != 0)
    			sp->assocparams.sasoc_asocmaxrxt =
    						assocparams.sasoc_asocmaxrxt;
    		if (assocparams.sasoc_cookie_life != 0)
    			sp->assocparams.sasoc_cookie_life =
    						assocparams.sasoc_cookie_life;
    	}
    	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_setsockopt_mappedv4(struct sock *sk, char __user *optval, int optlen)
    {
    	int val;
    	struct sctp_sock *sp = sctp_sk(sk);
    
    	if (optlen < sizeof(int))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    	if (val)
    		sp->v4mapped = 1;
    	else
    		sp->v4mapped = 0;
    
    	return 0;
    }
    
    /*
     * 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_setsockopt_maxseg(struct sock *sk, char __user *optval, int optlen)
    {
    	struct sctp_association *asoc;
    	struct list_head *pos;
    	struct sctp_sock *sp = sctp_sk(sk);
    	int val;
    
    	if (optlen < sizeof(int))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    
    	if ((val != 0) && ((val < 8) || (val > SCTP_MAX_CHUNK_LEN)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    	sp->user_frag = val;
    
    
    	/* Update the frag_point of the existing associations. */
    	list_for_each(pos, &(sp->ep->asocs)) {
    		asoc = list_entry(pos, struct sctp_association, asocs);
    
    		asoc->frag_point = sctp_frag_point(sp, asoc->pathmtu);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	return 0;
    }
    
    
    /*
     *  7.1.9 Set Peer Primary Address (SCTP_SET_PEER_PRIMARY_ADDR)
     *
     *   Requests that the peer mark the enclosed address as the association
     *   primary. The enclosed address must be one of the association's
     *   locally bound addresses. The following structure is used to make a
     *   set primary request:
     */
    static int sctp_setsockopt_peer_primary_addr(struct sock *sk, char __user *optval,
    					     int optlen)
    {
    	struct sctp_sock	*sp;
    	struct sctp_endpoint	*ep;
    	struct sctp_association	*asoc = NULL;
    	struct sctp_setpeerprim	prim;
    	struct sctp_chunk	*chunk;
    	int 			err;
    
    	sp = sctp_sk(sk);
    	ep = sp->ep;
    
    	if (!sctp_addip_enable)
    		return -EPERM;
    
    	if (optlen != sizeof(struct sctp_setpeerprim))
    		return -EINVAL;
    
    	if (copy_from_user(&prim, optval, optlen))
    		return -EFAULT;
    
    	asoc = sctp_id2assoc(sk, prim.sspp_assoc_id);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	if (!asoc->peer.asconf_capable)
    		return -EPERM;
    
    	if (asoc->peer.addip_disabled_mask & SCTP_PARAM_SET_PRIMARY)
    		return -EPERM;
    
    	if (!sctp_state(asoc, ESTABLISHED))
    		return -ENOTCONN;
    
    	if (!sctp_assoc_lookup_laddr(asoc, (union sctp_addr *)&prim.sspp_addr))
    		return -EADDRNOTAVAIL;
    
    	/* Create an ASCONF chunk with SET_PRIMARY parameter	*/
    	chunk = sctp_make_asconf_set_prim(asoc,
    					  (union sctp_addr *)&prim.sspp_addr);
    	if (!chunk)
    		return -ENOMEM;
    
    	err = sctp_send_asconf(asoc, chunk);
    
    	SCTP_DEBUG_PRINTK("We set peer primary addr primitively.\n");
    
    	return err;
    }
    
    
    static int sctp_setsockopt_adaptation_layer(struct sock *sk, char __user *optval,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					  int optlen)
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (optlen != sizeof(struct sctp_setadaptation))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    
    	if (copy_from_user(&adaptation, optval, optlen))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EFAULT;
    
    
    	sctp_sk(sk)->adaptation_ind = adaptation.ssb_adaptation_ind;
    
    /*
     * 7.1.29.  Set or Get the default context (SCTP_CONTEXT)
     *
     * The context field in the sctp_sndrcvinfo structure is normally only
     * used when a failed message is retrieved holding the value that was
     * sent down on the actual send call.  This option allows the setting of
     * a default context on an association basis that will be received on
     * reading messages from the peer.  This is especially helpful in the
     * one-2-many model for an application to keep some reference to an
     * internal state machine that is processing messages on the
     * association.  Note that the setting of this value only effects
     * received messages from the peer and does not effect the value that is
     * saved with outbound messages.
     */
    static int sctp_setsockopt_context(struct sock *sk, char __user *optval,
    				   int optlen)
    {
    	struct sctp_assoc_value params;
    	struct sctp_sock *sp;
    	struct sctp_association *asoc;
    
    	if (optlen != sizeof(struct sctp_assoc_value))
    		return -EINVAL;
    	if (copy_from_user(&params, optval, optlen))
    		return -EFAULT;
    
    	sp = sctp_sk(sk);
    
    	if (params.assoc_id != 0) {
    		asoc = sctp_id2assoc(sk, params.assoc_id);
    		if (!asoc)
    			return -EINVAL;
    		asoc->default_rcv_context = params.assoc_value;
    	} else {
    		sp->default_rcv_context = params.assoc_value;
    	}
    
    	return 0;
    }
    
    
    /*
     * 7.1.24.  Get or set fragmented interleave (SCTP_FRAGMENT_INTERLEAVE)
     *
     * This options will at a minimum specify if the implementation is doing
     * fragmented interleave.  Fragmented interleave, for a one to many
     * socket, is when subsequent calls to receive a message may return
     * parts of messages from different associations.  Some implementations
     * may allow you to turn this value on or off.  If so, when turned off,
     * no fragment interleave will occur (which will cause a head of line
     * blocking amongst multiple associations sharing the same one to many
     * socket).  When this option is turned on, then each receive call may
     * come from a different association (thus the user must receive data
     * with the extended calls (e.g. sctp_recvmsg) to keep track of which
     * association each receive belongs to.
     *
     * This option takes a boolean value.  A non-zero value indicates that
     * fragmented interleave is on.  A value of zero indicates that
     * fragmented interleave is off.
     *
     * Note that it is important that an implementation that allows this
     * option to be turned on, have it off by default.  Otherwise an unaware
     * application using the one to many model may become confused and act
     * incorrectly.
     */
    static int sctp_setsockopt_fragment_interleave(struct sock *sk,
    					       char __user *optval,
    					       int optlen)
    {
    	int val;
    
    	if (optlen != sizeof(int))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    
    	sctp_sk(sk)->frag_interleave = (val == 0) ? 0 : 1;
    
    	return 0;
    }
    
    
    /*
     * 7.1.25.  Set or Get the sctp partial delivery point
     *       (SCTP_PARTIAL_DELIVERY_POINT)
     * This option will set or get the SCTP partial delivery point.  This
     * point is the size of a message where the partial delivery API will be
     * invoked to help free up rwnd space for the peer.  Setting this to a
     * lower value will cause partial delivery's to happen more often.  The
     * calls argument is an integer that sets or gets the partial delivery
     * point.
     */
    static int sctp_setsockopt_partial_delivery_point(struct sock *sk,
    						  char __user *optval,
    						  int optlen)
    {
    	u32 val;
    
    	if (optlen != sizeof(u32))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    
    	sctp_sk(sk)->pd_point = val;
    
    	return 0; /* is this the right error code? */
    }
    
    
    /*
     * 7.1.28.  Set or Get the maximum burst (SCTP_MAX_BURST)
     *
     * This option will allow a user to change the maximum burst of packets
     * that can be emitted by this association.  Note that the default value
     * is 4, and some implementations may restrict this setting so that it
     * can only be lowered.
     *
     * NOTE: This text doesn't seem right.  Do this on a socket basis with
     * future associations inheriting the socket value.
     */
    static int sctp_setsockopt_maxburst(struct sock *sk,
    				    char __user *optval,
    				    int optlen)
    {
    	int val;
    
    	if (optlen != sizeof(int))
    		return -EINVAL;
    	if (get_user(val, (int __user *)optval))
    		return -EFAULT;
    
    	if (val < 0)
    		return -EINVAL;
    
    	sctp_sk(sk)->max_burst = val;
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* API 6.2 setsockopt(), getsockopt()
     *
     * Applications use setsockopt() and getsockopt() to set or retrieve
     * socket options.  Socket options are used to change the default
     * behavior of sockets calls.  They are described in Section 7.
     *
     * The syntax is:
     *
     *   ret = getsockopt(int sd, int level, int optname, void __user *optval,
     *                    int __user *optlen);
     *   ret = setsockopt(int sd, int level, int optname, const void __user *optval,
     *                    int optlen);
     *
     *   sd      - the socket descript.
     *   level   - set to IPPROTO_SCTP for all SCTP options.
     *   optname - the option name.
     *   optval  - the buffer to store the value of the option.
     *   optlen  - the size of the buffer.
     */
    SCTP_STATIC int sctp_setsockopt(struct sock *sk, int level, int optname,
    				char __user *optval, int optlen)
    {
    	int retval = 0;
    
    	SCTP_DEBUG_PRINTK("sctp_setsockopt(sk: %p... optname: %d)\n",
    			  sk, optname);
    
    	/* 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 setsockopt() 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->setsockopt(sk, level, optname, optval, optlen);
    		goto out_nounlock;
    	}
    
    	sctp_lock_sock(sk);
    
    	switch (optname) {
    	case SCTP_SOCKOPT_BINDX_ADD:
    		/* 'optlen' is the size of the addresses buffer. */
    		retval = sctp_setsockopt_bindx(sk, (struct sockaddr __user *)optval,
    					       optlen, SCTP_BINDX_ADD_ADDR);
    		break;
    
    	case SCTP_SOCKOPT_BINDX_REM:
    		/* 'optlen' is the size of the addresses buffer. */