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

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
...@@ -1044,157 +1044,160 @@ static int sctp_setsockopt_bindx(struct sock *sk, ...@@ -1044,157 +1044,160 @@ static int sctp_setsockopt_bindx(struct sock *sk,
return err; return err;
} }
/* __sctp_connect(struct sock* sk, struct sockaddr *kaddrs, int addrs_size) static int sctp_connect_new_asoc(struct sctp_endpoint *ep,
* const union sctp_addr *daddr,
* Common routine for handling connect() and sctp_connectx(). const struct sctp_initmsg *init,
* Connect will come in with just a single address. struct sctp_transport **tp)
*/
static int __sctp_connect(struct sock *sk,
struct sockaddr *kaddrs,
int addrs_size, int flags,
sctp_assoc_t *assoc_id)
{ {
struct sctp_association *asoc;
struct sock *sk = ep->base.sk;
struct net *net = sock_net(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; enum sctp_scope scope;
long timeo; int err;
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); if (sctp_endpoint_is_peeled_off(ep, daddr))
ep = sp->ep; return -EADDRNOTAVAIL;
/* connect() cannot be done on a socket that is already in ESTABLISHED if (!ep->base.bind_addr.port) {
* state - UDP-style peeled off socket or a TCP-style socket that if (sctp_autobind(sk))
* is already connected. return -EAGAIN;
* It cannot be done even on a TCP-style listening socket. } else {
*/ if (ep->base.bind_addr.port < inet_prot_sock(net) &&
if (sctp_sstate(sk, ESTABLISHED) || sctp_sstate(sk, CLOSING) || !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
(sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))) { return -EACCES;
err = -EISCONN;
goto out_free;
} }
/* Walk through the addrs buffer and count the number of addresses. */ scope = sctp_scope(daddr);
addr_buf = kaddrs; asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
while (walk_size < addrs_size) { if (!asoc)
struct sctp_af *af; return -ENOMEM;
if (walk_size + sizeof(sa_family_t) > addrs_size) { err = sctp_assoc_set_bind_addr_from_ep(asoc, scope, GFP_KERNEL);
err = -EINVAL; if (err < 0)
goto out_free; goto free;
*tp = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
if (!*tp) {
err = -ENOMEM;
goto free;
} }
sa_addr = addr_buf; if (!init)
af = sctp_get_af_specific(sa_addr->sa.sa_family); return 0;
/* If the address family is not supported or if this address if (init->sinit_num_ostreams) {
* causes the address buffer to overflow return EINVAL. __u16 outcnt = init->sinit_num_ostreams;
*/
if (!af || (walk_size + af->sockaddr_len) > addrs_size) { asoc->c.sinit_num_ostreams = outcnt;
err = -EINVAL; /* outcnt has been changed, need to re-init stream */
goto out_free; 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;
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);
return 0;
free:
sctp_association_free(asoc);
return err;
}
/* Save current address so we can work with it */ static int sctp_connect_add_peer(struct sctp_association *asoc,
memcpy(&to, sa_addr, af->sockaddr_len); union sctp_addr *daddr, int addr_len)
{
struct sctp_endpoint *ep = asoc->ep;
struct sctp_association *old;
struct sctp_transport *t;
int err;
err = sctp_verify_addr(sk, &to, af->sockaddr_len); err = sctp_verify_addr(ep->base.sk, daddr, addr_len);
if (err) if (err)
goto out_free; return err;
/* Make sure the destination port is correctly set old = sctp_endpoint_lookup_assoc(ep, daddr, &t);
* in all addresses. if (old && old != asoc)
*/ return old->state >= SCTP_STATE_ESTABLISHED ? -EISCONN
if (asoc && asoc->peer.port && asoc->peer.port != port) { : -EALREADY;
err = -EINVAL;
goto out_free;
}
/* Check if there already is a matching association on the if (sctp_endpoint_is_peeled_off(ep, daddr))
* endpoint (other than the one created here). return -EADDRNOTAVAIL;
*/
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;
}
/* If we could not find a matching association on the endpoint, t = sctp_assoc_add_peer(asoc, daddr, GFP_KERNEL, SCTP_UNKNOWN);
* make sure that there is no peeled-off association matching if (!t)
* the peer address even on another socket. return -ENOMEM;
*/
if (sctp_endpoint_is_peeled_off(ep, &to)) {
err = -EADDRNOTAVAIL;
goto out_free;
}
if (!asoc) { return 0;
/* 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 /* __sctp_connect(struct sock* sk, struct sockaddr *kaddrs, int addrs_size)
* equivalent to binding with a wildcard address. *
* Common routine for handling connect() and sctp_connectx().
* Connect will come in with just a single address.
*/ */
if (!ep->base.bind_addr.port) { static int __sctp_connect(struct sock *sk, struct sockaddr *kaddrs,
if (sctp_autobind(sk)) { int addrs_size, int flags, sctp_assoc_t *assoc_id)
err = -EAGAIN; {
goto out_free; struct sctp_sock *sp = sctp_sk(sk);
} struct sctp_endpoint *ep = sp->ep;
} else { struct sctp_transport *transport;
/* struct sctp_association *asoc;
* If an unprivileged user inherits a 1-many void *addr_buf = kaddrs;
* style socket with open associations on a union sctp_addr *daddr;
* privileged port, it MAY be permitted to struct sctp_af *af;
* accept new associations, but it SHOULD NOT int walk_size, err;
* be permitted to open new associations. long timeo;
*/
if (ep->base.bind_addr.port <
inet_prot_sock(net) &&
!ns_capable(net->user_ns,
CAP_NET_BIND_SERVICE)) {
err = -EACCES;
goto out_free;
}
}
scope = sctp_scope(&to); if (sctp_sstate(sk, ESTABLISHED) || sctp_sstate(sk, CLOSING) ||
asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL); (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING)))
if (!asoc) { return -EISCONN;
err = -ENOMEM;
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; goto out_free;
}
err = sctp_assoc_set_bind_addr_from_ep(asoc, scope, daddr = addr_buf;
GFP_KERNEL); af = sctp_get_af_specific(daddr->sa.sa_family);
if (err < 0) { if (!af || af->sockaddr_len + walk_size > addrs_size)
goto out_free; goto out_free;
}
} if (asoc->peer.port != ntohs(daddr->v4.sin_port))
goto out_free;
/* Prime the peer's transport structures. */ err = sctp_connect_add_peer(asoc, daddr, af->sockaddr_len);
transport = sctp_assoc_add_peer(asoc, &to, GFP_KERNEL, if (err)
SCTP_UNKNOWN);
if (!transport) {
err = -ENOMEM;
goto out_free; goto out_free;
}
addrcnt++;
addr_buf += af->sockaddr_len; addr_buf += af->sockaddr_len;
walk_size += af->sockaddr_len; walk_size += af->sockaddr_len;
} }
...@@ -1208,40 +1211,25 @@ static int __sctp_connect(struct sock *sk, ...@@ -1208,40 +1211,25 @@ static int __sctp_connect(struct sock *sk,
goto out_free; goto out_free;
} }
err = sctp_primitive_ASSOCIATE(net, asoc, NULL); err = sctp_primitive_ASSOCIATE(sock_net(sk), asoc, NULL);
if (err < 0) { if (err < 0)
goto out_free; goto out_free;
}
/* Initialize sk's dport and daddr for getpeername() */ /* Initialize sk's dport and daddr for getpeername() */
inet_sk(sk)->inet_dport = htons(asoc->peer.port); 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; sk->sk_err = 0;
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
if (assoc_id) if (assoc_id)
*assoc_id = asoc->assoc_id; *assoc_id = asoc->assoc_id;
err = sctp_wait_for_connect(asoc, &timeo); timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
/* Note: the asoc may be freed after the return of return sctp_wait_for_connect(asoc, &timeo);
* sctp_wait_for_connect.
*/
/* Don't free association on exit. */
asoc = NULL;
out_free: out_free:
pr_debug("%s: took out_free path with asoc:%p kaddrs:%p err:%d\n", pr_debug("%s: took out_free path with asoc:%p kaddrs:%p err:%d\n",
__func__, asoc, kaddrs, err); __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); sctp_association_free(asoc);
}
return err; return err;
} }
...@@ -1311,7 +1299,8 @@ static int __sctp_setsockopt_connectx(struct sock *sk, ...@@ -1311,7 +1299,8 @@ static int __sctp_setsockopt_connectx(struct sock *sk,
pr_debug("%s: sk:%p addrs:%p addrs_size:%d\n", pr_debug("%s: sk:%p addrs:%p addrs_size:%d\n",
__func__, sk, addrs, addrs_size); __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; return -EINVAL;
kaddrs = memdup_user(addrs, addrs_size); kaddrs = memdup_user(addrs, addrs_size);
...@@ -1659,9 +1648,7 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags, ...@@ -1659,9 +1648,7 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
struct sctp_transport **tp) struct sctp_transport **tp)
{ {
struct sctp_endpoint *ep = sctp_sk(sk)->ep; struct sctp_endpoint *ep = sctp_sk(sk)->ep;
struct net *net = sock_net(sk);
struct sctp_association *asoc; struct sctp_association *asoc;
enum sctp_scope scope;
struct cmsghdr *cmsg; struct cmsghdr *cmsg;
__be32 flowinfo = 0; __be32 flowinfo = 0;
struct sctp_af *af; struct sctp_af *af;
...@@ -1676,20 +1663,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags, ...@@ -1676,20 +1663,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
sctp_sstate(sk, CLOSING))) sctp_sstate(sk, CLOSING)))
return -EADDRNOTAVAIL; 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 /* Label connection socket for first association 1-to-many
* style for client sequence socket()->sendmsg(). This * style for client sequence socket()->sendmsg(). This
* needs to be done before sctp_assoc_add_peer() as that will * 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, ...@@ -1705,45 +1678,10 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
if (err < 0) if (err < 0)
return err; return err;
asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL); err = sctp_connect_new_asoc(ep, daddr, cmsgs->init, tp);
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);
if (err) if (err)
goto free; return err;
} asoc = (*tp)->asoc;
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;
}
if (!cmsgs->addrs_msg) if (!cmsgs->addrs_msg)
return 0; return 0;
...@@ -1753,8 +1691,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags, ...@@ -1753,8 +1691,6 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
/* sendv addr list parse */ /* sendv addr list parse */
for_each_cmsghdr(cmsg, cmsgs->addrs_msg) { for_each_cmsghdr(cmsg, cmsgs->addrs_msg) {
struct sctp_transport *transport;
struct sctp_association *old;
union sctp_addr _daddr; union sctp_addr _daddr;
int dlen; int dlen;
...@@ -1788,31 +1724,11 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags, ...@@ -1788,31 +1724,11 @@ static int sctp_sendmsg_new_asoc(struct sock *sk, __u16 sflags,
daddr->v6.sin6_port = htons(asoc->peer.port); daddr->v6.sin6_port = htons(asoc->peer.port);
memcpy(&daddr->v6.sin6_addr, CMSG_DATA(cmsg), dlen); 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, err = sctp_connect_add_peer(asoc, daddr, sizeof(*daddr));
SCTP_UNKNOWN); if (err)
if (!transport) {
err = -ENOMEM;
goto free; goto free;
} }
}
return 0; return 0;
......
...@@ -43,8 +43,8 @@ static struct sctp_transport *sctp_transport_init(struct net *net, ...@@ -43,8 +43,8 @@ static struct sctp_transport *sctp_transport_init(struct net *net,
gfp_t gfp) gfp_t gfp)
{ {
/* Copy in the address. */ /* Copy in the address. */
peer->ipaddr = *addr;
peer->af_specific = sctp_get_af_specific(addr->sa.sa_family); 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)); memset(&peer->saddr, 0, sizeof(union sctp_addr));
peer->sack_generation = 0; peer->sack_generation = 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment