Commit dd8378b3 authored by Xin Long's avatar Xin Long Committed by David S. Miller
Browse files

sctp: clean up __sctp_connect



__sctp_connect is doing quit similar things as sctp_sendmsg_new_asoc.
To factor out common functions, this patch is to clean up their code
to make them look more similar:

  1. create the asoc and add a peer with the 1st addr.
  2. add peers with the other addrs into this asoc one by one.

while at it, also remove the unused 'addrcnt'.

Signed-off-by: default avatarXin Long <lucien.xin@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f40f1177
Loading
Loading
Loading
Loading
+73 −136
Original line number Diff line number Diff line
@@ -1049,152 +1049,104 @@ out:
 * 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(struct sock *sk, struct sockaddr *kaddrs,
			  int addrs_size, int flags, sctp_assoc_t *assoc_id)
{
	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_association *old, *asoc;
	struct sctp_sock *sp = sctp_sk(sk);
	struct sctp_endpoint *ep = sp->ep;
	struct sctp_transport *transport;
	union sctp_addr to;
	struct net *net = sock_net(sk);
	void *addr_buf = kaddrs;
	union sctp_addr *daddr;
	enum sctp_scope scope;
	struct sctp_af *af;
	int walk_size, err;
	long timeo;
	int err = 0;
	int addrcnt = 0;
	int walk_size = 0;
	union sctp_addr *sa_addr = NULL;
	void *addr_buf;
	unsigned short port;

	sp = sctp_sk(sk);
	ep = sp->ep;

	/* 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;
	}
	    (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING)))
		return -EISCONN;

	/* Walk through the addrs buffer and count the number of addresses. */
	addr_buf = kaddrs;
	while (walk_size < addrs_size) {
		struct sctp_af *af;
	daddr = addr_buf;
	af = sctp_get_af_specific(daddr->sa.sa_family);
	if (!af || af->sockaddr_len > addrs_size)
		return -EINVAL;

		if (walk_size + sizeof(sa_family_t) > addrs_size) {
			err = -EINVAL;
			goto out_free;
		}
	err = sctp_verify_addr(sk, daddr, af->sockaddr_len);
	if (err)
		return err;

		sa_addr = addr_buf;
		af = sctp_get_af_specific(sa_addr->sa.sa_family);
	asoc = sctp_endpoint_lookup_assoc(ep, daddr, &transport);
	if (asoc)
		return asoc->state >= SCTP_STATE_ESTABLISHED ? -EISCONN
							     : -EALREADY;

		/* 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 (sctp_endpoint_is_peeled_off(ep, daddr))
		return -EADDRNOTAVAIL;

		port = ntohs(sa_addr->v4.sin_port);
	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;
	}

		/* Save current address so we can work with it */
		memcpy(&to, sa_addr, af->sockaddr_len);
	scope = sctp_scope(daddr);
	asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
	if (!asoc)
		return -ENOMEM;

		err = sctp_verify_addr(sk, &to, af->sockaddr_len);
		if (err)
	err = sctp_assoc_set_bind_addr_from_ep(asoc, scope, GFP_KERNEL);
	if (err < 0)
		goto out_free;

		/* Make sure the destination port is correctly set
		 * in all addresses.
		 */
		if (asoc && asoc->peer.port && asoc->peer.port != port) {
			err = -EINVAL;
	transport = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
	if (!transport) {
		err = -ENOMEM;
		goto out_free;
	}

		/* 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;
	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;
		}

		/* 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;
		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) {
			/* 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.
			 */
			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;
		if (asoc->peer.port != ntohs(daddr->v4.sin_port))
			goto out_free;
				}
			}

			scope = sctp_scope(&to);
			asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
			if (!asoc) {
				err = -ENOMEM;
		err = sctp_verify_addr(sk, daddr, af->sockaddr_len);
		if (err)
			goto out_free;
			}

			err = sctp_assoc_set_bind_addr_from_ep(asoc, scope,
							      GFP_KERNEL);
			if (err < 0) {
		old = sctp_endpoint_lookup_assoc(ep, daddr, &transport);
		if (old && old != asoc) {
			err = old->state >= SCTP_STATE_ESTABLISHED ? -EISCONN
								   : -EALREADY;
			goto out_free;
		}

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

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

		addrcnt++;
		addr_buf  += af->sockaddr_len;
		walk_size += af->sockaddr_len;
	}
@@ -1209,39 +1161,24 @@ static int __sctp_connect(struct sock *sk,
	}

	err = sctp_primitive_ASSOCIATE(net, asoc, NULL);
	if (err < 0) {
	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;
}