X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fsctp%2Finput.c;fp=net%2Fsctp%2Finput.c;h=2325fee8574819498067979aef55bfcd353c9a0a;hb=64ba3f394c830ec48a1c31b53dcae312c56f1604;hp=2060bbeb30148135735e9587feb40a4cd00c9d18;hpb=be1e6109ac94a859551f8e1774eb9a8469fe055c;p=linux-2.6.git diff --git a/net/sctp/input.c b/net/sctp/input.c index 2060bbeb3..2325fee85 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -73,8 +73,6 @@ static struct sctp_association *__sctp_lookup_association( const union sctp_addr *peer, struct sctp_transport **pt); -static void sctp_add_backlog(struct sock *sk, struct sk_buff *skb); - /* Calculate the SCTP checksum of an SCTP packet. */ static inline int sctp_rcv_checksum(struct sk_buff *skb) @@ -129,13 +127,14 @@ int sctp_rcv(struct sk_buff *skb) union sctp_addr dest; int family; struct sctp_af *af; + int ret = 0; if (skb->pkt_type!=PACKET_HOST) goto discard_it; SCTP_INC_STATS_BH(SCTP_MIB_INSCTPPACKS); - if (skb_linearize(skb)) + if (skb_linearize(skb, GFP_ATOMIC)) goto discard_it; sh = (struct sctphdr *) skb->h.raw; @@ -144,8 +143,7 @@ int sctp_rcv(struct sk_buff *skb) __skb_pull(skb, skb->h.raw - skb->data); if (skb->len < sizeof(struct sctphdr)) goto discard_it; - if ((skb->ip_summed != CHECKSUM_UNNECESSARY) && - (sctp_rcv_checksum(skb) < 0)) + if (sctp_rcv_checksum(skb) < 0) goto discard_it; skb_pull(skb, sizeof(struct sctphdr)); @@ -193,6 +191,7 @@ int sctp_rcv(struct sk_buff *skb) */ if (sk->sk_bound_dev_if && (sk->sk_bound_dev_if != af->skb_iif(skb))) { + sock_put(sk); if (asoc) { sctp_association_put(asoc); asoc = NULL; @@ -203,6 +202,7 @@ int sctp_rcv(struct sk_buff *skb) sk = sctp_get_ctl_sock(); ep = sctp_sk(sk)->ep; sctp_endpoint_hold(ep); + sock_hold(sk); rcvr = &ep->base; } @@ -231,13 +231,16 @@ int sctp_rcv(struct sk_buff *skb) goto discard_release; nf_reset(skb); - if (sk_filter(sk, skb, 1)) + ret = sk_filter(sk, skb, 1); + if (ret) goto discard_release; /* Create an SCTP packet structure. */ chunk = sctp_chunkify(skb, asoc, sk); - if (!chunk) + if (!chunk) { + ret = -ENOMEM; goto discard_release; + } SCTP_INPUT_CB(skb)->chunk = chunk; /* Remember what endpoint is to handle this packet. */ @@ -258,27 +261,35 @@ int sctp_rcv(struct sk_buff *skb) */ sctp_bh_lock_sock(sk); + /* It is possible that the association could have moved to a different + * socket if it is peeled off. If so, update the sk. + */ + if (sk != rcvr->sk) { + sctp_bh_lock_sock(rcvr->sk); + sctp_bh_unlock_sock(sk); + sk = rcvr->sk; + } + if (sock_owned_by_user(sk)) - sctp_add_backlog(sk, skb); + sk_add_backlog(sk, skb); else - sctp_inq_push(&chunk->rcvr->inqueue, chunk); + sctp_backlog_rcv(sk, skb); + /* Release the sock and the sock ref we took in the lookup calls. + * The asoc/ep ref will be released in sctp_backlog_rcv. + */ sctp_bh_unlock_sock(sk); + sock_put(sk); - /* Release the asoc/ep ref we took in the lookup calls. */ - if (asoc) - sctp_association_put(asoc); - else - sctp_endpoint_put(ep); - - return 0; + return ret; discard_it: kfree_skb(skb); - return 0; + return ret; discard_release: - /* Release the asoc/ep ref we took in the lookup calls. */ + /* Release any structures we may be holding. */ + sock_put(sk); if (asoc) sctp_association_put(asoc); else @@ -287,87 +298,56 @@ discard_release: goto discard_it; } -/* Process the backlog queue of the socket. Every skb on - * the backlog holds a ref on an association or endpoint. - * We hold this ref throughout the state machine to make - * sure that the structure we need is still around. +/* Handle second half of inbound skb processing. If the sock was busy, + * we may have need to delay processing until later when the sock is + * released (on the backlog). If not busy, we call this routine + * directly from the bottom half. */ int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb) { struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk; - struct sctp_inq *inqueue = &chunk->rcvr->inqueue; + struct sctp_inq *inqueue = NULL; struct sctp_ep_common *rcvr = NULL; - int backloged = 0; rcvr = chunk->rcvr; - /* If the rcvr is dead then the association or endpoint - * has been deleted and we can safely drop the chunk - * and refs that we are holding. - */ - if (rcvr->dead) { - sctp_chunk_free(chunk); - goto done; - } - - if (unlikely(rcvr->sk != sk)) { - /* In this case, the association moved from one socket to - * another. We are currently sitting on the backlog of the - * old socket, so we need to move. - * However, since we are here in the process context we - * need to take make sure that the user doesn't own - * the new socket when we process the packet. - * If the new socket is user-owned, queue the chunk to the - * backlog of the new socket without dropping any refs. - * Otherwise, we can safely push the chunk on the inqueue. - */ - - sk = rcvr->sk; - sctp_bh_lock_sock(sk); - - if (sock_owned_by_user(sk)) { - sk_add_backlog(sk, skb); - backloged = 1; - } else - sctp_inq_push(inqueue, chunk); - - sctp_bh_unlock_sock(sk); - - /* If the chunk was backloged again, don't drop refs */ - if (backloged) - return 0; - } else { - sctp_inq_push(inqueue, chunk); - } - -done: - /* Release the refs we took in sctp_add_backlog */ - if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type) - sctp_association_put(sctp_assoc(rcvr)); - else if (SCTP_EP_TYPE_SOCKET == rcvr->type) - sctp_endpoint_put(sctp_ep(rcvr)); - else - BUG(); - + BUG_TRAP(rcvr->sk == sk); + + if (rcvr->dead) { + sctp_chunk_free(chunk); + } else { + inqueue = &chunk->rcvr->inqueue; + sctp_inq_push(inqueue, chunk); + } + + /* Release the asoc/ep ref we took in the lookup calls in sctp_rcv. */ + if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type) + sctp_association_put(sctp_assoc(rcvr)); + else + sctp_endpoint_put(sctp_ep(rcvr)); + return 0; } -static void sctp_add_backlog(struct sock *sk, struct sk_buff *skb) +void sctp_backlog_migrate(struct sctp_association *assoc, + struct sock *oldsk, struct sock *newsk) { - struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk; - struct sctp_ep_common *rcvr = chunk->rcvr; - - /* Hold the assoc/ep while hanging on the backlog queue. - * This way, we know structures we need will not disappear from us - */ - if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type) - sctp_association_hold(sctp_assoc(rcvr)); - else if (SCTP_EP_TYPE_SOCKET == rcvr->type) - sctp_endpoint_hold(sctp_ep(rcvr)); - else - BUG(); + struct sk_buff *skb; + struct sctp_chunk *chunk; - sk_add_backlog(sk, skb); + skb = oldsk->sk_backlog.head; + oldsk->sk_backlog.head = oldsk->sk_backlog.tail = NULL; + while (skb != NULL) { + struct sk_buff *next = skb->next; + + chunk = SCTP_INPUT_CB(skb)->chunk; + skb->next = NULL; + if (&assoc->base == chunk->rcvr) + sk_add_backlog(newsk, skb); + else + sk_add_backlog(oldsk, skb); + skb = next; + } } /* Handle icmp frag needed error. */ @@ -440,7 +420,7 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb, union sctp_addr daddr; struct sctp_af *af; struct sock *sk = NULL; - struct sctp_association *asoc; + struct sctp_association *asoc = NULL; struct sctp_transport *transport = NULL; *app = NULL; *tpp = NULL; @@ -481,6 +461,7 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb, return sk; out: + sock_put(sk); if (asoc) sctp_association_put(asoc); return NULL; @@ -490,6 +471,7 @@ out: void sctp_err_finish(struct sock *sk, struct sctp_association *asoc) { sctp_bh_unlock_sock(sk); + sock_put(sk); if (asoc) sctp_association_put(asoc); } @@ -516,7 +498,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info) int type = skb->h.icmph->type; int code = skb->h.icmph->code; struct sock *sk; - struct sctp_association *asoc = NULL; + struct sctp_association *asoc; struct sctp_transport *transport; struct inet_sock *inet; char *saveip, *savesctp; @@ -742,6 +724,7 @@ static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *l hit: sctp_endpoint_hold(ep); + sock_hold(epb->sk); read_unlock(&head->lock); return ep; } @@ -843,6 +826,7 @@ static struct sctp_association *__sctp_lookup_association( hit: *pt = transport; sctp_association_hold(asoc); + sock_hold(epb->sk); read_unlock(&head->lock); return asoc; } @@ -870,6 +854,7 @@ int sctp_has_association(const union sctp_addr *laddr, struct sctp_transport *transport; if ((asoc = sctp_lookup_association(laddr, paddr, &transport))) { + sock_put(asoc->base.sk); sctp_association_put(asoc); return 1; }