Skip to content
Snippets Groups Projects
socket.c 183 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed

/* 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))
	len = sizeof(struct sctp_assoc_value);

	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))
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_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))
Linus Torvalds's avatar
Linus Torvalds committed
		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;
	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;

	if (len < sizeof(struct sctp_getaddrs_old))
Linus Torvalds's avatar
Linus Torvalds committed
		return -EINVAL;

	len = sizeof(struct sctp_getaddrs_old);

	if (copy_from_user(&getaddrs, optval, len))
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_entry(from, &asoc->peer.transport_addr_list,
				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 (put_user(len, optlen))
		return -EFAULT;
	if (copy_to_user(optval, &getaddrs, 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(sk->sk_family)->sockaddr_len;
			return -ENOMEM;
		if (copy_to_user(to, &temp, addrlen))
			return -EFAULT;
		to += addrlen;
		cnt++;
		space_left -= addrlen;
	}

	if (put_user(cnt, &((struct sctp_getaddrs __user *)optval)->addr_num))
		return -EFAULT;
	bytes_copied = ((char __user *)to) - optval;
	if (put_user(bytes_copied, optlen))
Linus Torvalds's avatar
Linus Torvalds committed
		return -EFAULT;

	return 0;
}

static int sctp_getsockopt_local_addrs_num_old(struct sock *sk, int len,
					       char __user *optval,
					       int __user *optlen)
Linus Torvalds's avatar
Linus Torvalds committed
{
	sctp_assoc_t id;
	struct sctp_bind_addr *bp;
	struct sctp_association *asoc;
	struct sctp_sockaddr_entry *addr;
	int cnt = 0;

	if (len < sizeof(sctp_assoc_t))
Linus Torvalds's avatar
Linus Torvalds committed
		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;
	} else {
		asoc = sctp_id2assoc(sk, id);
		if (!asoc)
			return -EINVAL;
		bp = &asoc->base.bind_addr;
	}

	/* 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)) {
			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))
Linus Torvalds's avatar
Linus Torvalds committed
					continue;
Linus Torvalds's avatar
Linus Torvalds committed
				cnt++;
			}
Linus Torvalds's avatar
Linus Torvalds committed
		} else {
			cnt = 1;
		}
		goto done;
	}

	/* Protection on the bound address list is not needed,
	 * since in the socket option context we hold the socket lock,
	 * so there is no way that the bound address list can change.
	 */
	list_for_each_entry(addr, &bp->address_list, list) {
Linus Torvalds's avatar
Linus Torvalds committed
		cnt ++;
	}
done:
	return cnt;
}

/* Helper function that copies local addresses to user and returns the number
 * of addresses copied.
 */
static int sctp_copy_laddrs_old(struct sock *sk, __u16 port,
					int max_addrs, void *to,
					int *bytes_copied)
Linus Torvalds's avatar
Linus Torvalds committed
{
	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))
Linus Torvalds's avatar
Linus Torvalds committed
			continue;
		memcpy(&temp, &addr->a, sizeof(temp));
		if (!temp.v4.sin_port)
			temp.v4.sin_port = htons(port);

Linus Torvalds's avatar
Linus Torvalds committed
		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sctp_sk(sk),
								&temp);
		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
Linus Torvalds's avatar
Linus Torvalds committed
		to += addrlen;
Linus Torvalds's avatar
Linus Torvalds committed
		cnt ++;
		if (cnt >= max_addrs) break;
	}
Linus Torvalds's avatar
Linus Torvalds committed

	return cnt;
}

static int sctp_copy_laddrs(struct sock *sk, __u16 port, void *to,
			    size_t space_left, int *bytes_copied)
{
	struct sctp_sockaddr_entry *addr;
	union sctp_addr temp;
	int cnt = 0;
	int addrlen;

	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))
		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;

	return cnt;
}

/* Old API for getting list of local addresses. Does not work for 32-bit
 * programs running on a 64-bit kernel
 */
static int sctp_getsockopt_local_addrs_old(struct sock *sk, int len,
					   char __user *optval, int __user *optlen)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct sctp_bind_addr *bp;
	struct sctp_association *asoc;
	int cnt = 0;
Linus Torvalds's avatar
Linus Torvalds committed
	struct sctp_sockaddr_entry *addr;
	void __user *to;
	union sctp_addr temp;
	struct sctp_sock *sp = sctp_sk(sk);
	int addrlen;
	int err = 0;
Linus Torvalds's avatar
Linus Torvalds committed

	if (len < sizeof(struct sctp_getaddrs_old))
Linus Torvalds's avatar
Linus Torvalds committed
		return -EINVAL;

	len = sizeof(struct sctp_getaddrs_old);
	if (copy_from_user(&getaddrs, optval, len))
Linus Torvalds's avatar
Linus Torvalds committed
		return -EFAULT;

	if (getaddrs.addr_num <= 0) return -EINVAL;
	/*
	 *  For UDP-style sockets, id specifies the association to query.
	 *  If the id field is set to the value '0' then the locally bound
	 *  addresses are returned without regard to any particular
	 *  association.
	 */
	if (0 == getaddrs.assoc_id) {
		bp = &sctp_sk(sk)->ep->base.bind_addr;
	} else {
		asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
		if (!asoc)
			return -EINVAL;
		bp = &asoc->base.bind_addr;
	}

	to = getaddrs.addrs;

	/* Allocate space for a local instance of packed array to hold all
	 * the data.  We store addresses here first and then put write them
	 * to the user in one shot.
	 */
	addrs = kmalloc(sizeof(union sctp_addr) * getaddrs.addr_num,
			GFP_KERNEL);
	if (!addrs)
		return -ENOMEM;

Linus Torvalds's avatar
Linus Torvalds committed
	/* If the endpoint is bound to 0.0.0.0 or ::0, get the valid
	 * addresses from the global local address list.
	 */
	if (sctp_list_single_entry(&bp->address_list)) {
		addr = list_entry(bp->address_list.next,
				  struct sctp_sockaddr_entry, list);
		if (sctp_is_any(&addr->a)) {
			cnt = sctp_copy_laddrs_old(sk, bp->port,
						   getaddrs.addr_num,
						   addrs, &bytes_copied);
			goto copy_getaddrs;
	/* 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));
Linus Torvalds's avatar
Linus Torvalds committed
		sctp_get_pf_specific(sk->sk_family)->addr_v4map(sp, &temp);
		addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
		memcpy(buf, &temp, addrlen);
		buf += addrlen;
Linus Torvalds's avatar
Linus Torvalds committed
		cnt ++;
		if (cnt >= getaddrs.addr_num) break;
	}

copy_getaddrs:
	/* copy the entire address list into the user provided space */
	if (copy_to_user(to, addrs, bytes_copied)) {
		err = -EFAULT;
		goto error;
	}

	/* copy the leading structure back to user */
Linus Torvalds's avatar
Linus Torvalds committed
	getaddrs.addr_num = cnt;
	if (copy_to_user(optval, &getaddrs, len))
Linus Torvalds's avatar
Linus Torvalds committed
		err = -EFAULT;

Linus Torvalds's avatar
Linus Torvalds committed
	return err;
}

static int sctp_getsockopt_local_addrs(struct sock *sk, int len,
				       char __user *optval, int __user *optlen)
{
	struct sctp_bind_addr *bp;
	struct sctp_association *asoc;
	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);
		if (sctp_is_any(&addr->a)) {
			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
/*
 * 7.1.17 Set the maximum fragrmentation size (SCTP_MAXSEG)
 *
 * This socket option specifies the maximum size to put in any outgoing
 * SCTP chunk.  If a message is larger than this size it will be
 * fragmented by SCTP into the specified size.  Note that the underlying
 * SCTP implementation may fragment into smaller sized chunks when the
 * PMTU of the underlying association is smaller than the value set by
 * the user.
 */
static int sctp_getsockopt_maxseg(struct sock *sk, int len,
				  char __user *optval, int __user *optlen)
{
	int val;

	if (len < sizeof(int))
		return -EINVAL;

	len = sizeof(int);

	val = sctp_sk(sk)->user_frag;
	if (put_user(len, optlen))
		return -EFAULT;
	if (copy_to_user(optval, &val, len))
		return -EFAULT;

	return 0;
}

/*
 * 7.1.24.  Get or set fragmented interleave (SCTP_FRAGMENT_INTERLEAVE)
 * (chapter and verse is quoted at sctp_setsockopt_fragment_interleave())
 */
static int sctp_getsockopt_fragment_interleave(struct sock *sk, int len,
					       char __user *optval, int __user *optlen)
{
	int val;

	if (len < sizeof(int))
		return -EINVAL;

	len = sizeof(int);

	val = sctp_sk(sk)->frag_interleave;
	if (put_user(len, optlen))
		return -EFAULT;
	if (copy_to_user(optval, &val, len))
		return -EFAULT;

	return 0;
}

/*
 * 7.1.25.  Set or Get the sctp partial delivery point
 * (chapter and verse is quoted at sctp_setsockopt_partial_delivery_point())
 */
static int sctp_getsockopt_partial_delivery_point(struct sock *sk, int len,
						  char __user *optval,
						  int __user *optlen)
{

	if (len < sizeof(u32))
		return -EINVAL;

	len = sizeof(u32);

	val = sctp_sk(sk)->pd_point;
	if (put_user(len, optlen))
		return -EFAULT;
	if (copy_to_user(optval, &val, len))
		return -EFAULT;

	return -ENOTSUPP;
}