Commit ef012690 authored by Sridhar Samudrala's avatar Sridhar Samudrala

sctp: Fixes a couple of sctp_peeloff() issues.

* data/notifications waiting in the parent socket's receive queue
  should be moved to the peeled-off socket's receive queue.
* sendmsg() to a peer already on an association that has been
  peeled off should fail and not create a new association.
parent 6d727be7
......@@ -1044,6 +1044,9 @@ sctp_association_t *sctp_endpoint_lookup_assoc(const sctp_endpoint_t *ep,
sctp_endpoint_t *sctp_endpoint_is_match(sctp_endpoint_t *,
const sockaddr_storage_t *);
int sctp_has_association(const sockaddr_storage_t *laddr,
const sockaddr_storage_t *paddr);
int sctp_verify_init(const sctp_association_t *asoc,
sctp_cid_t cid,
sctp_init_chunk_t *peer_init,
......
......@@ -522,9 +522,9 @@ void __sctp_unhash_established(sctp_association_t *asoc)
}
/* Look up an association. */
sctp_association_t *__sctp_rcv_lookup_association(const sockaddr_storage_t *laddr,
const sockaddr_storage_t *paddr,
sctp_transport_t **transportp)
sctp_association_t *__sctp_lookup_association(const sockaddr_storage_t *laddr,
const sockaddr_storage_t *paddr,
sctp_transport_t **transportp)
{
sctp_hashbucket_t *head;
sctp_endpoint_common_t *epb;
......@@ -557,6 +557,36 @@ sctp_association_t *__sctp_rcv_lookup_association(const sockaddr_storage_t *ladd
return asoc;
}
/* Look up an association. BH-safe. */
sctp_association_t *sctp_lookup_association(const sockaddr_storage_t *laddr,
const sockaddr_storage_t *paddr,
sctp_transport_t **transportp)
{
sctp_association_t *asoc;
sctp_local_bh_disable();
asoc = __sctp_lookup_association(laddr, paddr, transportp);
sctp_local_bh_enable();
return asoc;
}
/* Is there an association matching the given local and peer addresses? */
int sctp_has_association(const sockaddr_storage_t *laddr,
const sockaddr_storage_t *paddr)
{
sctp_association_t *asoc;
sctp_transport_t *transport;
if (asoc = sctp_lookup_association(laddr, paddr, &transport)) {
sock_put(asoc->base.sk);
sctp_association_put(asoc);
return 1;
}
return 0;
}
/*
* SCTP Implementors Guide, 2.18 Handling of address
* parameters within the INIT or INIT-ACK.
......@@ -628,7 +658,7 @@ static sctp_association_t *__sctp_rcv_initack_lookup(struct sk_buff *skb,
sctp_param2sockaddr(paddr, parm, ntohs(sh->source));
asoc = __sctp_rcv_lookup_association(laddr, paddr, transportp);
asoc = __sctp_lookup_association(laddr, paddr, transportp);
if (asoc)
return asoc;
}
......@@ -644,7 +674,7 @@ sctp_association_t *__sctp_rcv_lookup(struct sk_buff *skb,
{
sctp_association_t *asoc;
asoc = __sctp_rcv_lookup_association(laddr, paddr, transportp);
asoc = __sctp_lookup_association(laddr, paddr, transportp);
/* Further lookup for INIT-ACK packet.
* SCTP Implementors Guide, 2.18 Handling of address
......
......@@ -820,7 +820,32 @@ SCTP_STATIC int sctp_sendmsg(struct sock *sk, struct msghdr *msg, int size)
/* If a msg_name has been specified, assume this is to be used. */
if (msg_name) {
/* Look for a matching association on the endpoint. */
asoc = sctp_endpoint_lookup_assoc(ep, &to, &transport);
if (!asoc) {
struct list_head *pos;
struct sockaddr_storage_list *addr;
sctp_bind_addr_t *bp = &ep->base.bind_addr;
sctp_read_lock(&ep->base.addr_lock);
/* If we could not find a matching association on
* the endpoint, make sure that there is no peeled-
* off association.
*/
list_for_each(pos, &bp->address_list) {
addr = list_entry(pos,
struct sockaddr_storage_list,
list);
if (sctp_has_association(&addr->a, &to)) {
err = -EINVAL;
sctp_read_unlock(&ep->base.addr_lock);
goto out_unlock;
}
}
sctp_read_unlock(&ep->base.addr_lock);
}
} else {
/* For a peeled-off socket, ignore any associd specified by
* the user with SNDRCVINFO.
......@@ -1583,6 +1608,8 @@ SCTP_STATIC int sctp_do_peeloff(sctp_association_t *assoc, struct socket **newso
sctp_endpoint_t *newep;
sctp_opt_t *oldsp = sctp_sk(oldsk);
sctp_opt_t *newsp;
struct sk_buff *skb, *tmp;
sctp_ulpevent_t *event;
int err = 0;
/* An association cannot be branched off from an already peeled-off
......@@ -1612,6 +1639,17 @@ SCTP_STATIC int sctp_do_peeloff(sctp_association_t *assoc, struct socket **newso
*/
newsp->ep = newep;
/* Move any messages in the old socket's receive queue that are for the
* peeled off association to the new socket's receive queue.
*/
sctp_skb_for_each(skb, &oldsk->receive_queue, tmp) {
event = (sctp_ulpevent_t *)skb->cb;
if (event->asoc == assoc) {
__skb_unlink(skb, skb->list);
__skb_queue_tail(&newsk->receive_queue, skb);
}
}
/* Set the type of socket to indicate that it is peeled off from the
* original socket.
*/
......@@ -1629,39 +1667,46 @@ static inline int sctp_getsockopt_peeloff(struct sock *sk, int len, char *optval
{
sctp_peeloff_arg_t peeloff;
struct socket *newsock;
int err, sd;
int retval = 0;
sctp_association_t *assoc;
if (len != sizeof(sctp_peeloff_arg_t))
return -EINVAL;
if (copy_from_user(&peeloff, optval, len))
return -EFAULT;
sctp_lock_sock(sk);
assoc = sctp_id2assoc(sk, peeloff.associd);
if (NULL == assoc)
return -EINVAL;
if (NULL == assoc) {
retval = -EINVAL;
goto out_unlock;
}
SCTP_DEBUG_PRINTK("%s: sk: %p assoc: %p\n", __FUNCTION__, sk, assoc);
err = sctp_do_peeloff(assoc, &newsock);
if (err < 0)
return err;
retval = sctp_do_peeloff(assoc, &newsock);
if (retval < 0)
goto out_unlock;
/* Map the socket to an unused fd that can be returned to the user. */
sd = sock_map_fd(newsock);
if (sd < 0) {
retval = sock_map_fd(newsock);
if (retval < 0) {
sock_release(newsock);
return sd;
goto out_unlock;
}
SCTP_DEBUG_PRINTK("%s: sk: %p assoc: %p newsk: %p sd: %d\n",
__FUNCTION__, sk, assoc, newsock->sk, sd);
__FUNCTION__, sk, assoc, newsock->sk, retval);
/* Return the fd mapped to the new socket. */
peeloff.sd = sd;
peeloff.sd = retval;
if (copy_to_user(optval, &peeloff, len))
return -EFAULT;
retval = -EFAULT;
return 0;
out_unlock:
sctp_release_sock(sk);
return retval;
}
SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
......
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