Skip to content
Snippets Groups Projects
socket.c 168 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    	sctp_release_sock(sk);
    	return err;
    }
    
    /* FIXME: Write comments. */
    SCTP_STATIC int sctp_disconnect(struct sock *sk, int flags)
    {
    	return -EOPNOTSUPP; /* STUB */
    }
    
    /* 4.1.4 accept() - TCP Style Syntax
     *
     * Applications use accept() call to remove an established SCTP
     * association from the accept queue of the endpoint.  A new socket
     * descriptor will be returned from accept() to represent the newly
     * formed association.
     */
    SCTP_STATIC struct sock *sctp_accept(struct sock *sk, int flags, int *err)
    {
    	struct sctp_sock *sp;
    	struct sctp_endpoint *ep;
    	struct sock *newsk = NULL;
    	struct sctp_association *asoc;
    	long timeo;
    	int error = 0;
    
    	sctp_lock_sock(sk);
    
    	sp = sctp_sk(sk);
    	ep = sp->ep;
    
    	if (!sctp_style(sk, TCP)) {
    		error = -EOPNOTSUPP;
    		goto out;
    	}
    
    	if (!sctp_sstate(sk, LISTENING)) {
    		error = -EINVAL;
    		goto out;
    	}
    
    
    	timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	error = sctp_wait_for_accept(sk, timeo);
    	if (error)
    		goto out;
    
    	/* We treat the list of associations on the endpoint as the accept
    	 * queue and pick the first association on the list.
    	 */
    	asoc = list_entry(ep->asocs.next, struct sctp_association, asocs);
    
    	newsk = sp->pf->create_accept_sk(sk, asoc);
    	if (!newsk) {
    		error = -ENOMEM;
    		goto out;
    	}
    
    	/* Populate the fields of the newsk from the oldsk and migrate the
    	 * asoc to the newsk.
    	 */
    	sctp_sock_migrate(sk, newsk, asoc, SCTP_SOCKET_TCP);
    
    out:
    	sctp_release_sock(sk);
    
    	*err = error;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return newsk;
    }
    
    /* The SCTP ioctl handler. */
    SCTP_STATIC int sctp_ioctl(struct sock *sk, int cmd, unsigned long arg)
    {
    	return -ENOIOCTLCMD;
    }
    
    /* This is the function which gets called during socket creation to
     * initialized the SCTP-specific portion of the sock.
     * The sock structure should already be zero-filled memory.
     */
    SCTP_STATIC int sctp_init_sock(struct sock *sk)
    {
    	struct sctp_endpoint *ep;
    	struct sctp_sock *sp;
    
    	SCTP_DEBUG_PRINTK("sctp_init_sock(sk: %p)\n", sk);
    
    	sp = sctp_sk(sk);
    
    	/* Initialize the SCTP per socket area.  */
    	switch (sk->sk_type) {
    	case SOCK_SEQPACKET:
    		sp->type = SCTP_SOCKET_UDP;
    		break;
    	case SOCK_STREAM:
    		sp->type = SCTP_SOCKET_TCP;
    		break;
    	default:
    		return -ESOCKTNOSUPPORT;
    	}
    
    	/* Initialize default send parameters. These parameters can be
    	 * modified with the SCTP_DEFAULT_SEND_PARAM socket option.
    	 */
    	sp->default_stream = 0;
    	sp->default_ppid = 0;
    	sp->default_flags = 0;
    	sp->default_context = 0;
    	sp->default_timetolive = 0;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Initialize default setup parameters. These parameters
    	 * can be modified with the SCTP_INITMSG socket option or
    	 * overridden by the SCTP_INIT CMSG.
    	 */
    	sp->initmsg.sinit_num_ostreams   = sctp_max_outstreams;
    	sp->initmsg.sinit_max_instreams  = sctp_max_instreams;
    	sp->initmsg.sinit_max_attempts   = sctp_max_retrans_init;
    
    	sp->initmsg.sinit_max_init_timeo = sctp_rto_max;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Initialize default RTO related parameters.  These parameters can
    	 * be modified for with the SCTP_RTOINFO socket option.
    	 */
    
    	sp->rtoinfo.srto_initial = sctp_rto_initial;
    	sp->rtoinfo.srto_max     = sctp_rto_max;
    	sp->rtoinfo.srto_min     = sctp_rto_min;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Initialize default association related parameters. These parameters
    	 * can be modified with the SCTP_ASSOCINFO socket option.
    	 */
    	sp->assocparams.sasoc_asocmaxrxt = sctp_max_retrans_association;
    	sp->assocparams.sasoc_number_peer_destinations = 0;
    	sp->assocparams.sasoc_peer_rwnd = 0;
    	sp->assocparams.sasoc_local_rwnd = 0;
    
    	sp->assocparams.sasoc_cookie_life = sctp_valid_cookie_life;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Initialize default event subscriptions. By default, all the
    
    	 * options are off.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    	memset(&sp->subscribe, 0, sizeof(struct sctp_event_subscribe));
    
    	/* Default Peer Address Parameters.  These defaults can
    	 * be modified via SCTP_PEER_ADDR_PARAMS
    	 */
    
    	sp->hbinterval  = sctp_hb_interval;
    
    	sp->pathmaxrxt  = sctp_max_retrans_path;
    	sp->pathmtu     = 0; // allow default discovery
    
    	sp->sackdelay   = sctp_sack_timeout;
    
    			  SPP_PMTUD_ENABLE |
    			  SPP_SACKDELAY_ENABLE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* If enabled no SCTP message fragmentation will be performed.
    	 * Configure through SCTP_DISABLE_FRAGMENTS socket option.
    	 */
    	sp->disable_fragments = 0;
    
    
    	/* Enable Nagle algorithm by default.  */
    	sp->nodelay           = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Enable by default. */
    	sp->v4mapped          = 1;
    
    	/* Auto-close idle associations after the configured
    	 * number of seconds.  A value of 0 disables this
    	 * feature.  Configure through the SCTP_AUTOCLOSE socket option,
    	 * for UDP-style sockets only.
    	 */
    	sp->autoclose         = 0;
    
    	/* User specified fragmentation limit. */
    	sp->user_frag         = 0;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sp->pf = sctp_get_pf_specific(sk->sk_family);
    
    	/* Control variables for partial data delivery. */
    
    	atomic_set(&sp->pd_mode, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	skb_queue_head_init(&sp->pd_lobby);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Create a per socket endpoint structure.  Even if we
    	 * change the data structure relationships, this may still
    	 * be useful for storing pre-connect address information.
    	 */
    	ep = sctp_endpoint_new(sk, GFP_KERNEL);
    	if (!ep)
    		return -ENOMEM;
    
    	sp->ep = ep;
    	sp->hmac = NULL;
    
    	SCTP_DBG_OBJCNT_INC(sock);
    	return 0;
    }
    
    /* Cleanup any SCTP per socket resources.  */
    SCTP_STATIC int sctp_destroy_sock(struct sock *sk)
    {
    	struct sctp_endpoint *ep;
    
    	SCTP_DEBUG_PRINTK("sctp_destroy_sock(sk: %p)\n", sk);
    
    	/* Release our hold on the endpoint. */
    	ep = sctp_sk(sk)->ep;
    	sctp_endpoint_free(ep);
    
    	return 0;
    }
    
    /* API 4.1.7 shutdown() - TCP Style Syntax
     *     int shutdown(int socket, int how);
     *
     *     sd      - the socket descriptor of the association to be closed.
     *     how     - Specifies the type of shutdown.  The  values  are
     *               as follows:
     *               SHUT_RD
     *                     Disables further receive operations. No SCTP
     *                     protocol action is taken.
     *               SHUT_WR
     *                     Disables further send operations, and initiates
     *                     the SCTP shutdown sequence.
     *               SHUT_RDWR
     *                     Disables further send  and  receive  operations
     *                     and initiates the SCTP shutdown sequence.
     */
    SCTP_STATIC void sctp_shutdown(struct sock *sk, int how)
    {
    	struct sctp_endpoint *ep;
    	struct sctp_association *asoc;
    
    	if (!sctp_style(sk, TCP))
    		return;
    
    	if (how & SEND_SHUTDOWN) {
    		ep = sctp_sk(sk)->ep;
    		if (!list_empty(&ep->asocs)) {
    			asoc = list_entry(ep->asocs.next,
    					  struct sctp_association, asocs);
    			sctp_primitive_SHUTDOWN(asoc, NULL);
    		}
    	}
    }
    
    /* 7.2.1 Association Status (SCTP_STATUS)
    
     * Applications can retrieve current status information about an
     * association, including association state, peer receiver window size,
     * number of unacked data chunks, and number of data chunks pending
     * receipt.  This information is read-only.
     */
    static int sctp_getsockopt_sctp_status(struct sock *sk, int len,
    				       char __user *optval,
    				       int __user *optlen)
    {
    	struct sctp_status status;
    	struct sctp_association *asoc = NULL;
    	struct sctp_transport *transport;
    	sctp_assoc_t associd;
    	int retval = 0;
    
    	if (len != sizeof(status)) {
    		retval = -EINVAL;
    		goto out;
    	}
    
    	if (copy_from_user(&status, optval, sizeof(status))) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    	associd = status.sstat_assoc_id;
    	asoc = sctp_id2assoc(sk, associd);
    	if (!asoc) {
    		retval = -EINVAL;
    		goto out;
    	}
    
    	transport = asoc->peer.primary_path;
    
    	status.sstat_assoc_id = sctp_assoc2id(asoc);
    	status.sstat_state = asoc->state;
    	status.sstat_rwnd =  asoc->peer.rwnd;
    	status.sstat_unackdata = asoc->unack_data;
    
    	status.sstat_penddata = sctp_tsnmap_pending(&asoc->peer.tsn_map);
    	status.sstat_instrms = asoc->c.sinit_max_instreams;
    	status.sstat_outstrms = asoc->c.sinit_num_ostreams;
    	status.sstat_fragmentation_point = asoc->frag_point;
    	status.sstat_primary.spinfo_assoc_id = sctp_assoc2id(transport->asoc);
    
    	memcpy(&status.sstat_primary.spinfo_address, &transport->ipaddr,
    			transport->af_specific->sockaddr_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Map ipv4 address into v4-mapped-on-v6 address.  */
    	sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
    		(union sctp_addr *)&status.sstat_primary.spinfo_address);
    
    	status.sstat_primary.spinfo_state = transport->state;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	status.sstat_primary.spinfo_cwnd = transport->cwnd;
    	status.sstat_primary.spinfo_srtt = transport->srtt;
    	status.sstat_primary.spinfo_rto = jiffies_to_msecs(transport->rto);
    
    	status.sstat_primary.spinfo_mtu = transport->pathmtu;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (status.sstat_primary.spinfo_state == SCTP_UNKNOWN)
    		status.sstat_primary.spinfo_state = SCTP_ACTIVE;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (put_user(len, optlen)) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    	SCTP_DEBUG_PRINTK("sctp_getsockopt_sctp_status(%d): %d %d %d\n",
    			  len, status.sstat_state, status.sstat_rwnd,
    			  status.sstat_assoc_id);
    
    	if (copy_to_user(optval, &status, len)) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    out:
    	return (retval);
    }
    
    
    /* 7.2.2 Peer Address Information (SCTP_GET_PEER_ADDR_INFO)
     *
     * Applications can retrieve information about a specific peer address
     * of an association, including its reachability state, congestion
     * window, and retransmission timer values.  This information is
     * read-only.
     */
    static int sctp_getsockopt_peer_addr_info(struct sock *sk, int len,
    					  char __user *optval,
    					  int __user *optlen)
    {
    	struct sctp_paddrinfo pinfo;
    	struct sctp_transport *transport;
    	int retval = 0;
    
    	if (len != sizeof(pinfo)) {
    		retval = -EINVAL;
    		goto out;
    	}
    
    	if (copy_from_user(&pinfo, optval, sizeof(pinfo))) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    	transport = sctp_addr_id2transport(sk, &pinfo.spinfo_address,
    					   pinfo.spinfo_assoc_id);
    	if (!transport)
    		return -EINVAL;
    
    	pinfo.spinfo_assoc_id = sctp_assoc2id(transport->asoc);
    
    	pinfo.spinfo_state = transport->state;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	pinfo.spinfo_cwnd = transport->cwnd;
    	pinfo.spinfo_srtt = transport->srtt;
    	pinfo.spinfo_rto = jiffies_to_msecs(transport->rto);
    
    	pinfo.spinfo_mtu = transport->pathmtu;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (pinfo.spinfo_state == SCTP_UNKNOWN)
    		pinfo.spinfo_state = SCTP_ACTIVE;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (put_user(len, optlen)) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    	if (copy_to_user(optval, &pinfo, len)) {
    		retval = -EFAULT;
    		goto out;
    	}
    
    out:
    	return (retval);
    }
    
    /* 7.1.12 Enable/Disable message fragmentation (SCTP_DISABLE_FRAGMENTS)
     *
     * This option is a on/off flag.  If enabled no SCTP message
     * fragmentation will be performed.  Instead if a message being sent
     * exceeds the current PMTU size, the message will NOT be sent and
     * instead a error will be indicated to the user.
     */
    static int sctp_getsockopt_disable_fragments(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)->disable_fragments == 1);
    	if (put_user(len, optlen))
    		return -EFAULT;
    	if (copy_to_user(optval, &val, len))
    		return -EFAULT;
    	return 0;
    }
    
    /* 7.1.15 Set notification and ancillary events (SCTP_EVENTS)
     *
     * This socket option is used to specify various notifications and
     * ancillary data the user wishes to receive.
     */
    static int sctp_getsockopt_events(struct sock *sk, int len, char __user *optval,
    				  int __user *optlen)
    {
    	if (len != sizeof(struct sctp_event_subscribe))
    		return -EINVAL;
    	if (copy_to_user(optval, &sctp_sk(sk)->subscribe, len))
    		return -EFAULT;
    	return 0;
    }
    
    /* 7.1.8 Automatic Close of associations (SCTP_AUTOCLOSE)
     *
     * 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;
    	if (len != sizeof(int))
    		return -EINVAL;
    	if (copy_to_user(optval, &sctp_sk(sk)->autoclose, len))
    		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;
    
    	struct inet_sock *inetsk;
    
    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;
    
    	/* 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);
    
    
    	/* Make peeled-off sockets more like 1-1 accepted sockets.
    	 * Set the daddr and initialize id to something more random
    	 */
    	inetsk = inet_sk(sock->sk);
    	inetsk->daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
    	inetsk->id = asoc->next_tsn ^ jiffies;
    
    
    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))
    		return -EINVAL;
    	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", __FUNCTION__, sk, asoc);
    
    	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);
    	if (retval < 0) {
    		sock_release(newsock);
    		goto out;
    	}
    
    	SCTP_DEBUG_PRINTK("%s: sk: %p asoc: %p newsk: %p sd: %d\n",
    			  __FUNCTION__, sk, asoc, newsock->sk, retval);
    
    	/* Return the fd mapped to the new socket.  */
    	peeloff.sd = retval;
    	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))
    		return -EINVAL;
    
    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(( 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. 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_getsockopt_delayed_ack_time(struct sock *sk, int len,
    					    char __user *optval,
    					    int __user *optlen)
    {
    	struct sctp_assoc_value  params;
    	struct sctp_association *asoc = NULL;
    	struct sctp_sock        *sp = sctp_sk(sk);
    
    	if (len != sizeof(struct sctp_assoc_value))
    		return - EINVAL;
    
    	if (copy_from_user(&params, optval, len))
    		return -EFAULT;
    
    	/* 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 (asoc) {
    		/* Fetch association values. */
    		if (asoc->param_flags & SPP_SACKDELAY_ENABLE)
    			params.assoc_value = jiffies_to_msecs(
    				asoc->sackdelay);
    		else
    			params.assoc_value = 0;
    	} else {
    		/* Fetch socket values. */
    		if (sp->param_flags & SPP_SACKDELAY_ENABLE)
    			params.assoc_value  = sp->sackdelay;
    		else
    			params.assoc_value  = 0;
    	}
    
    	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))
    		return -EINVAL;
    	if (copy_to_user(optval, &sctp_sk(sk)->initmsg, len))
    		return -EFAULT;
    	return 0;
    }
    
    
    static int sctp_getsockopt_peer_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_association *asoc;
    	struct list_head *pos;
    	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.  */
    	asoc = sctp_id2assoc(sk, id);
    	if (!asoc)
    		return -EINVAL;
    
    	list_for_each(pos, &asoc->peer.transport_addr_list) {
    		cnt ++;
    	}
    
    	return cnt;
    }
    
    
     * Old API for getting list of peer addresses. Does not work for 32-bit
     * programs running on a 64-bit kernel
     */
    static int sctp_getsockopt_peer_addrs_old(struct sock *sk, int len,
    					  char __user *optval,
    					  int __user *optlen)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct sctp_association *asoc;
    	struct list_head *pos;
    	int cnt = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct sctp_transport *from;
    	void __user *to;
    	union sctp_addr temp;
    	struct sctp_sock *sp = sctp_sk(sk);
    	int addrlen;
    
    
    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.  */
    	asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
    	if (!asoc)
    		return -EINVAL;
    
    	to = (void __user *)getaddrs.addrs;
    	list_for_each(pos, &asoc->peer.transport_addr_list) {
    		from = list_entry(pos, struct sctp_transport, transports);
    
    		memcpy(&temp, &from->ipaddr, 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(sk->sk_family)->sockaddr_len;
    		if (copy_to_user(to, &temp, addrlen))
    			return -EFAULT;
    		to += addrlen ;
    		cnt ++;
    		if (cnt >= getaddrs.addr_num) break;
    	}
    	getaddrs.addr_num = cnt;
    
    	if (copy_to_user(optval, &getaddrs, sizeof(struct sctp_getaddrs_old)))
    		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;
    	struct list_head *pos;
    	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 - sizeof(struct sctp_getaddrs) -
    
    			offsetof(struct sctp_getaddrs,addrs);
    
    	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;
    		if(space_left < addrlen)
    			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_to_user_old(struct sock *sk, __u16 port, int max_addrs,
    					void __user *to)
    
    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;
    
    		if (copy_to_user(to, &temp, addrlen))