Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ca04afb authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'sctp-clean-up-sctp_connect-function'



Xin Long says:

====================
sctp: clean up __sctp_connect function

This patchset is to factor out some common code for
sctp_sendmsg_new_asoc() and __sctp_connect() into 2
new functioins.

v1->v2:
  - add the patch 1/5 to avoid a slab-out-of-bounds warning.
  - add some code comment for the check change in patch 2/5.
  - remove unused 'addrcnt' as Marcelo noticed in patch 3/5.
====================

Acked-by: default avatarMarcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 1db88c53 a64e59c7
Loading
Loading
Loading
Loading
+146 −230
Original line number Diff line number Diff line
@@ -1044,157 +1044,160 @@ static int sctp_setsockopt_bindx(struct sock *sk,
	return err;
}

/* __sctp_connect(struct sock* sk, struct sockaddr *kaddrs, int addrs_size)
 *
 * Common routine for handling connect() and sctp_connectx().
 * Connect will come in with just a single address.
 */
static int __sctp_connect(struct sock *sk,
			  struct sockaddr *kaddrs,
			  int addrs_size, int flags,
			  sctp_assoc_t *assoc_id)
static int sctp_connect_new_asoc(struct sctp_endpoint *ep,
				 const union sctp_addr *daddr,
				 const struct sctp_initmsg *init,
				 struct sctp_transport **tp)
{
	struct sctp_association *asoc;
	struct sock *sk = ep->base.sk;
	struct net *net = sock_net(sk);
	struct sctp_sock *sp;
	struct sctp_endpoint *ep;
	struct sctp_association *asoc = NULL;
	struct sctp_association *asoc2;
	struct sctp_transport *transport;
	union sctp_addr to;
	enum sctp_scope scope;
	long timeo;
	int err = 0;
	int addrcnt = 0;
	int walk_size = 0;
	union sctp_addr *sa_addr = NULL;
	void *addr_buf;
	unsigned short port;
	int err;

	sp = sctp_sk(sk);
	ep = sp->ep;
	if (sctp_endpoint_is_peeled_off(ep, daddr))
		return -EADDRNOTAVAIL;

	/* connect() cannot be done on a socket that is already in ESTABLISHED
	 * state - UDP-style peeled off socket or a TCP-style socket that
	 * is already connected.
	 * It cannot be done even on a TCP-style listening socket.
	 */
	if (sctp_sstate(sk, ESTABLISHED) || sctp_sstate(sk, CLOSING) ||
	    (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))) {
		err = -EISCONN;
		goto out_free;
	if (!ep->base.bind_addr.port) {
		if (sctp_autobind(sk))
			return -EAGAIN;
	} else {
		if (ep->base.bind_addr.port < inet_prot_sock(net) &&
		    !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
			return -EACCES;
	}

	/* Walk through the addrs buffer and count the number of addresses. */
	addr_buf = kaddrs;
	while (walk_size < addrs_size) {
		struct sctp_af *af;
	scope = sctp_scope(daddr);
	asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
	if (!asoc)
		return -ENOMEM;

		if (walk_size + sizeof(sa_family_t) > addrs_size) {
			err = -EINVAL;
			goto out_free;
	err = sctp_assoc_set_bind_addr_from_ep(asoc, scope, GFP_KERNEL);
	if (err < 0)
		goto free;

	*tp = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
	if (!*tp) {
		err = -ENOMEM;
		goto free;
	}

		sa_addr = addr_buf;
		af = sctp_get_af_specific(sa_addr->sa.sa_family);
	if (!init)
		return 0;

		/* If the address family is not supported or if this address
		 * causes the address buffer to overflow return EINVAL.
		 */
		if (!af || (walk_size + af->sockaddr_len) > addrs_size) {
			err = -EINVAL;
			goto out_free;
	if (init->sinit_num_ostreams) {
		__u16 outcnt = init->sinit_num_ostreams;

		asoc->c.sinit_num_ostreams = outcnt;
		/* outcnt has been changed, need to re-init stream */
		err = sctp_stream_init(&asoc->stream, outcnt, 0, GFP_KERNEL);
		if (err)
			goto free;
	}

		port = ntohs(sa_addr->v4.sin_port);
	if (init->sinit_max_instreams)
		asoc->c.sinit_max_instreams = init->sinit_max_instreams;

		/* Save current address so we can work with it */
		memcpy(&to, sa_addr, af->sockaddr_len);
	if (init->sinit_max_attempts)
		asoc->max_init_attempts = init->sinit_max_attempts;

		err = sctp_verify_addr(sk, &to, af->sockaddr_len);
		if (err)
			goto out_free;
	if (init->sinit_max_init_timeo)
		asoc->max_init_timeo =
			msecs_to_jiffies(init->sinit_max_init_timeo);

		/* Make sure the destination port is correctly set
		 * in all addresses.
		 */
		if (asoc && asoc->peer.port && asoc->peer.port != port) {
			err = -EINVAL;
			goto out_free;
	return 0;
free:
	sctp_association_free(asoc);
	return err;
}

		/* Check if there already is a matching association on the
		 * endpoint (other than the one created here).
		 */
		asoc2 = sctp_endpoint_lookup_assoc(ep, &to, &transport);
		if (asoc2 && asoc2 != asoc) {
			if (asoc2->state >= SCTP_STATE_ESTABLISHED)
				err = -EISCONN;
			else
				err = -EALREADY;
			goto out_free;
		}
static int sctp_connect_add_peer(struct sctp_association *asoc,
				 union sctp_addr *daddr, int addr_len)
{
	struct sctp_endpoint *ep = asoc->ep;
	struct sctp_association *old;
	struct sctp_transport *t;
	int err;

		/* If we could not find a matching association on the endpoint,
		 * make sure that there is no peeled-off association matching
		 * the peer address even on another socket.
		 */
		if (sctp_endpoint_is_peeled_off(ep, &to)) {
			err = -EADDRNOTAVAIL;
			goto out_free;
	err = sctp_verify_addr(ep->base.sk, daddr, addr_len);
	if (err)
		return err;

	old = sctp_endpoint_lookup_assoc(ep, daddr, &t);
	if (old && old != asoc)
		return old->state >= SCTP_STATE_ESTABLISHED ? -EISCONN
							    : -EALREADY;

	if (sctp_endpoint_is_peeled_off(ep, daddr))
		return -EADDRNOTAVAIL;

	t = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
	if (!t)
		return -ENOMEM;

	return 0;
}

		if (!asoc) {
			/* If a bind() or sctp_bindx() is not called prior to
			 * an sctp_connectx() call, the system picks an
			 * ephemeral port and will choose an address set
			 * equivalent to binding with a wildcard address.
/* __sctp_connect(struct sock* sk, struct sockaddr *kaddrs, int addrs_size)
 *
 * Common routine for handling connect() and sctp_connectx().
 * Connect will come in with just a single address.
 */
			if (!ep->base.bind_addr.port) {
				if (sctp_autobind(sk)) {
					err = -EAGAIN;
					goto out_free;
				}
			} else {
				/*
				 * If an unprivileged user inherits a 1-many
				 * style socket with open associations on a
				 * privileged port, it MAY be permitted to
				 * accept new associations, but it SHOULD NOT
				 * be permitted to open new associations.
				 */
				if (ep->base.bind_addr.port <
				    inet_prot_sock(net) &&
				    !ns_capable(net->user_ns,
				    CAP_NET_BIND_SERVICE)) {
					err = -EACCES;
					goto out_free;
				}
			}
static int __sctp_connect(struct sock *sk, struct sockaddr *kaddrs,
			  int addrs_size, int flags, sctp_assoc_t *assoc_id)
{
	struct sctp_sock *sp = sctp_sk(sk);
	struct sctp_endpoint *ep = sp->ep;
	struct sctp_transport *transport;
	struct sctp_association *asoc;
	void *addr_buf = kaddrs;
	union sctp_addr *daddr;
	struct sctp_af *af;
	int walk_size, err;
	long timeo;

			scope = sctp_scope(&to);
			asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
			if (!asoc) {
				err = -ENOMEM;
	if (sctp_sstate(sk, ESTABLISHED) || sctp_sstate(sk, CLOSING) ||
	    (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING)))
		return -EISCONN;

	daddr = addr_buf;
	af = sctp_get_af_specific(daddr->sa.sa_family);
	if (!af || af->sockaddr_len > addrs_size)
		return -EINVAL;

	err = sctp_verify_addr(sk, daddr, af->sockaddr_len);
	if (err)
		return err;

	asoc = sctp_endpoint_lookup_assoc(ep, daddr, &transport);
	if (asoc)
		return asoc->state >= SCTP_STATE_ESTABLISHED ? -EISCONN
							     : -EALREADY;

	err = sctp_connect_new_asoc(ep, daddr, NULL, &transport);
	if (err)
		return err;
	asoc = transport->asoc;

	addr_buf += af->sockaddr_len;
	walk_size = af->sockaddr_len;
	while (walk_size < addrs_size) {
		err = -EINVAL;
		if (walk_size + sizeof(sa_family_t) > addrs_size)
			goto out_free;
			}

			err = sctp_assoc_set_bind_addr_from_ep(asoc, scope,
							      GFP_KERNEL);
			if (err < 0) {
		daddr = addr_buf;
		af = sctp_get_af_specific(daddr->sa.sa_family);
		if (!af || af->sockaddr_len + walk_size > addrs_size)
			goto out_free;
			}

		}
		if (asoc->peer.port != ntohs(daddr->v4.sin_port))
			goto out_free;

		/* Prime the peer's transport structures.  */
		transport = sctp_assoc_add_peer(asoc, &to, GFP_KERNEL,
						SCTP_UNKNOWN);
		if (!transport) {
			err = -ENOMEM;
		err = sctp_connect_add_peer(asoc, daddr, af->sockaddr_len);
		if (err)
			goto out_free;
		}

		addrcnt++;
		addr_buf  += af->sockaddr_len;
		walk_size += af->sockaddr_len;
	}
@@ -1208,40 +1211,25 @@ static int __sctp_connect(struct sock *sk,
			goto out_free;
	}

	err = sctp_primitive_ASSOCIATE(net, asoc, NULL);
	if (err < 0) {
	err = sctp_primitive_ASSOCIATE(sock_net(sk), asoc, NULL);
	if (err < 0)
		goto out_free;
	}

	/* Initialize sk's dport and daddr for getpeername() */
	inet_sk(sk)->inet_dport = htons(asoc->peer.port);
	sp->pf->to_sk_daddr(sa_addr, sk);
	sp->pf->to_sk_daddr(daddr, sk);
	sk->sk_err = 0;

	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);

	if (assoc_id)
		*assoc_id = asoc->assoc_id;

	err = sctp_wait_for_connect(asoc, &timeo);
	/* Note: the asoc may be freed after the return of
	 * sctp_wait_for_connect.
	 */

	/* Don't free association on exit. */
	asoc = NULL;
	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
	return sctp_wait_for_connect(asoc, &timeo);

out_free:
	pr_debug("%s: took out_free path with asoc:%p kaddrs:%p err:%d\n",
		 __func__, asoc, kaddrs, err);

	if (asoc) {
		/* sctp_primitive_ASSOCIATE may have added this association
		 * To the hash table, try to unhash it, just in case, its a noop
		 * if it wasn't hashed so we're safe
		 */
	sctp_association_free(asoc);
	}
	return err;
}

@@ -1311,7 +1299,8 @@ static int __sctp_setsockopt_connectx(struct sock *sk,
	pr_debug("%s: sk:%p addrs:%p addrs_size:%d\n",
		 __func__, sk, addrs, addrs_size);

	if (unlikely(addrs_size <= 0))
	/* make sure the 1st addr's sa_family is accessible later */
	if (unlikely(addrs_size < sizeof(sa_family_t)))
		return -EINVAL;

	kaddrs = memdup_user(addrs, addrs_size);
@@ -1659,9 +1648,7 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
				 struct sctp_transport **tp)
{
	struct sctp_endpoint *ep = sctp_sk(sk)->ep;
	struct net *net = sock_net(sk);
	struct sctp_association *asoc;
	enum sctp_scope scope;
	struct cmsghdr *cmsg;
	__be32 flowinfo = 0;
	struct sctp_af *af;
@@ -1676,20 +1663,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
				    sctp_sstate(sk, CLOSING)))
		return -EADDRNOTAVAIL;

	if (sctp_endpoint_is_peeled_off(ep, daddr))
		return -EADDRNOTAVAIL;

	if (!ep->base.bind_addr.port) {
		if (sctp_autobind(sk))
			return -EAGAIN;
	} else {
		if (ep->base.bind_addr.port < inet_prot_sock(net) &&
		    !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
			return -EACCES;
	}

	scope = sctp_scope(daddr);

	/* Label connection socket for first association 1-to-many
	 * style for client sequence socket()->sendmsg(). This
	 * needs to be done before sctp_assoc_add_peer() as that will
@@ -1705,45 +1678,10 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
	if (err < 0)
		return err;

	asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
	if (!asoc)
		return -ENOMEM;

	if (sctp_assoc_set_bind_addr_from_ep(asoc, scope, GFP_KERNEL) < 0) {
		err = -ENOMEM;
		goto free;
	}

	if (cmsgs->init) {
		struct sctp_initmsg *init = cmsgs->init;

		if (init->sinit_num_ostreams) {
			__u16 outcnt = init->sinit_num_ostreams;

			asoc->c.sinit_num_ostreams = outcnt;
			/* outcnt has been changed, need to re-init stream */
			err = sctp_stream_init(&asoc->stream, outcnt, 0,
					       GFP_KERNEL);
	err = sctp_connect_new_asoc(ep, daddr, cmsgs->init, tp);
	if (err)
				goto free;
		}

		if (init->sinit_max_instreams)
			asoc->c.sinit_max_instreams = init->sinit_max_instreams;

		if (init->sinit_max_attempts)
			asoc->max_init_attempts = init->sinit_max_attempts;

		if (init->sinit_max_init_timeo)
			asoc->max_init_timeo =
				msecs_to_jiffies(init->sinit_max_init_timeo);
	}

	*tp = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
	if (!*tp) {
		err = -ENOMEM;
		goto free;
	}
		return err;
	asoc = (*tp)->asoc;

	if (!cmsgs->addrs_msg)
		return 0;
@@ -1753,8 +1691,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,

	/* sendv addr list parse */
	for_each_cmsghdr(cmsg, cmsgs->addrs_msg) {
		struct sctp_transport *transport;
		struct sctp_association *old;
		union sctp_addr _daddr;
		int dlen;

@@ -1788,31 +1724,11 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
			daddr->v6.sin6_port = htons(asoc->peer.port);
			memcpy(&daddr->v6.sin6_addr, CMSG_DATA(cmsg), dlen);
		}
		err = sctp_verify_addr(sk, daddr, sizeof(*daddr));
		if (err)
			goto free;

		old = sctp_endpoint_lookup_assoc(ep, daddr, &transport);
		if (old && old != asoc) {
			if (old->state >= SCTP_STATE_ESTABLISHED)
				err = -EISCONN;
			else
				err = -EALREADY;
			goto free;
		}

		if (sctp_endpoint_is_peeled_off(ep, daddr)) {
			err = -EADDRNOTAVAIL;
			goto free;
		}

		transport = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL,
						SCTP_UNKNOWN);
		if (!transport) {
			err = -ENOMEM;
		err = sctp_connect_add_peer(asoc, daddr, sizeof(*daddr));
		if (err)
			goto free;
	}
	}

	return 0;

+1 −1
Original line number Diff line number Diff line
@@ -43,8 +43,8 @@ static struct sctp_transport *sctp_transport_init(struct net *net,
						  gfp_t gfp)
{
	/* Copy in the address.  */
	peer->ipaddr = *addr;
	peer->af_specific = sctp_get_af_specific(addr->sa.sa_family);
	memcpy(&peer->ipaddr, addr, peer->af_specific->sockaddr_len);
	memset(&peer->saddr, 0, sizeof(union sctp_addr));

	peer->sack_generation = 0;