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)
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, GFP_ATOMIC))
+ if (skb_linearize(skb))
goto discard_it;
sh = (struct sctphdr *) skb->h.raw;
__skb_pull(skb, skb->h.raw - skb->data);
if (skb->len < sizeof(struct sctphdr))
goto discard_it;
- if (sctp_rcv_checksum(skb) < 0)
+ if ((skb->ip_summed != CHECKSUM_UNNECESSARY) &&
+ (sctp_rcv_checksum(skb) < 0))
goto discard_it;
skb_pull(skb, sizeof(struct sctphdr));
*/
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;
sk = sctp_get_ctl_sock();
ep = sctp_sk(sk)->ep;
sctp_endpoint_hold(ep);
- sock_hold(sk);
rcvr = &ep->base;
}
}
}
- /* SCTP seems to always need a timestamp right now (FIXME) */
- if (skb->tstamp.off_sec == 0) {
- __net_timestamp(skb);
- sock_enable_timestamp(sk);
- }
-
if (!xfrm_policy_check(sk, XFRM_POLICY_IN, skb, family))
goto discard_release;
nf_reset(skb);
- ret = sk_filter(sk, skb, 1);
- if (ret)
+ if (sk_filter(sk, skb))
goto discard_release;
/* Create an SCTP packet structure. */
chunk = sctp_chunkify(skb, asoc, sk);
- if (!chunk) {
- ret = -ENOMEM;
+ if (!chunk)
goto discard_release;
- }
SCTP_INPUT_CB(skb)->chunk = chunk;
/* Remember what endpoint is to handle this packet. */
*/
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_INC_STATS_BH(SCTP_MIB_IN_PKT_BACKLOG);
+ sctp_add_backlog(sk, skb);
+ } else {
+ SCTP_INC_STATS_BH(SCTP_MIB_IN_PKT_SOFTIRQ);
+ sctp_inq_push(&chunk->rcvr->inqueue, chunk);
}
- if (sock_owned_by_user(sk))
- sk_add_backlog(sk, skb);
- else
- 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);
- return ret;
+ /* Release the asoc/ep ref we took in the lookup calls. */
+ if (asoc)
+ sctp_association_put(asoc);
+ else
+ sctp_endpoint_put(ep);
+
+ return 0;
discard_it:
+ SCTP_INC_STATS_BH(SCTP_MIB_IN_PKT_DISCARDS);
kfree_skb(skb);
- return ret;
+ return 0;
discard_release:
- /* Release any structures we may be holding. */
- sock_put(sk);
+ /* Release the asoc/ep ref we took in the lookup calls. */
if (asoc)
sctp_association_put(asoc);
else
goto discard_it;
}
-/* 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.
+/* 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.
*/
int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
- struct sctp_inq *inqueue = NULL;
+ struct sctp_inq *inqueue = &chunk->rcvr->inqueue;
struct sctp_ep_common *rcvr = NULL;
+ int backloged = 0;
rcvr = chunk->rcvr;
- 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));
-
+ /* 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();
+
return 0;
}
-void sctp_backlog_migrate(struct sctp_association *assoc,
- struct sock *oldsk, struct sock *newsk)
+static void sctp_add_backlog(struct sock *sk, struct sk_buff *skb)
{
- struct sk_buff *skb;
- struct sctp_chunk *chunk;
+ struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
+ struct sctp_ep_common *rcvr = chunk->rcvr;
- 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;
- }
+ /* 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();
+
+ sk_add_backlog(sk, skb);
}
/* Handle icmp frag needed error. */
* pmtu discovery on this transport.
*/
t->pathmtu = SCTP_DEFAULT_MINSEGMENT;
- t->param_flags = (t->param_flags & ~SPP_HB) |
+ t->param_flags = (t->param_flags & ~SPP_PMTUD) |
SPP_PMTUD_DISABLE;
} else {
t->pathmtu = pmtu;
union sctp_addr daddr;
struct sctp_af *af;
struct sock *sk = NULL;
- struct sctp_association *asoc = NULL;
+ struct sctp_association *asoc;
struct sctp_transport *transport = NULL;
*app = NULL; *tpp = NULL;
return sk;
out:
- sock_put(sk);
if (asoc)
sctp_association_put(asoc);
return NULL;
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);
}
int type = skb->h.icmph->type;
int code = skb->h.icmph->code;
struct sock *sk;
- struct sctp_association *asoc;
+ struct sctp_association *asoc = NULL;
struct sctp_transport *transport;
struct inet_sock *inet;
char *saveip, *savesctp;
struct sctp_endpoint *ep;
int hash;
- hash = sctp_ep_hashfn(laddr->v4.sin_port);
+ hash = sctp_ep_hashfn(ntohs(laddr->v4.sin_port));
head = &sctp_ep_hashtable[hash];
read_lock(&head->lock);
for (epb = head->chain; epb; epb = epb->next) {
hit:
sctp_endpoint_hold(ep);
- sock_hold(epb->sk);
read_unlock(&head->lock);
return ep;
}
/* Add an association to the hash. Local BH-safe. */
void sctp_hash_established(struct sctp_association *asoc)
{
+ if (asoc->temp)
+ return;
+
sctp_local_bh_disable();
__sctp_hash_established(asoc);
sctp_local_bh_enable();
/* Remove association from the hash table. Local BH-safe. */
void sctp_unhash_established(struct sctp_association *asoc)
{
+ if (asoc->temp)
+ return;
+
sctp_local_bh_disable();
__sctp_unhash_established(asoc);
sctp_local_bh_enable();
/* Optimize here for direct hit, only listening connections can
* have wildcards anyways.
*/
- hash = sctp_assoc_hashfn(local->v4.sin_port, peer->v4.sin_port);
+ hash = sctp_assoc_hashfn(ntohs(local->v4.sin_port), ntohs(peer->v4.sin_port));
head = &sctp_assoc_hashtable[hash];
read_lock(&head->lock);
for (epb = head->chain; epb; epb = epb->next) {
hit:
*pt = transport;
sctp_association_hold(asoc);
- sock_hold(epb->sk);
read_unlock(&head->lock);
return asoc;
}
struct sctp_transport *transport;
if ((asoc = sctp_lookup_association(laddr, paddr, &transport))) {
- sock_put(asoc->base.sk);
sctp_association_put(asoc);
return 1;
}
if (!af)
continue;
- af->from_addr_param(paddr, params.addr, ntohs(sh->source), 0);
+ af->from_addr_param(paddr, params.addr, sh->source, 0);
asoc = __sctp_lookup_association(laddr, paddr, &transport);
if (asoc)