Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 253b60e4 authored by Sean Tranchetti's avatar Sean Tranchetti
Browse files

rmnet_core: check tcp flags when segmenting packets



Now that the hardware will coalesce additional flags into the RSC frame,
we must check the TCP flags explicitly and remove ones that make no sense
when segmenting the RSC frame for any reason.

In particular, we must check for TCP_FIN, and clear this flag from any
segmetents of the RSSC frame except the last to avoid breaking the TCP
stream integrity.

Change-Id: I7f80437991821ab29cd190a9d5bd45e9cdb4c333
Signed-off-by: default avatarSean Tranchetti <stranche@codeaurora.org>
parent 1b790126
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -849,6 +849,16 @@ static struct sk_buff *rmnet_alloc_skb(struct rmnet_frag_descriptor *frag_desc,
		th->seq = frag_desc->tcp_seq;
	}

	if (frag_desc->tcp_flags_set) {
		struct tcphdr *th;
		__be16 *flags;

		th = (struct tcphdr *)
		     (rmnet_map_data_ptr(head_skb) + frag_desc->ip_len);
		flags = (__be16 *)&tcp_flag_word(th);
		*flags = frag_desc->tcp_flags;
	}

	/* Handle csum offloading */
	if (frag_desc->csum_valid && frag_desc->hdrs_valid) {
		/* Set the partial checksum information */
@@ -984,6 +994,24 @@ static void __rmnet_frag_segment_data(struct rmnet_frag_descriptor *coal_desc,
		new_desc->tcp_seq_set = 1;
		new_desc->tcp_seq = htonl(ntohl(th->seq) +
					  coal_desc->data_offset);

		/* Don't allow any dangerous flags to appear in any segments
		 * other than the last.
		 */
		if (th->fin || th->psh) {
			if (offset + dlen < coal_desc->len) {
				__be32 flag_word = tcp_flag_word(th);

				/* Clear the FIN and PSH flags from this
				 * segment.
				 */
				flag_word &= ~TCP_FLAG_FIN;
				flag_word &= ~TCP_FLAG_PSH;

				new_desc->tcp_flags_set = 1;
				new_desc->tcp_flags = *((__be16 *)&flag_word);
			}
		}
	} else if (coal_desc->trans_proto == IPPROTO_UDP) {
		struct udphdr *uh, __uh;

+3 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ struct rmnet_frag_descriptor {
	u32 hash;
	__be32 tcp_seq;
	__be16 ip_id;
	__be16 tcp_flags;
	u16 data_offset;
	u16 gso_size;
	u16 gso_segs;
@@ -53,7 +54,8 @@ struct rmnet_frag_descriptor {
	   ip_id_set:1,
	   tcp_seq_set:1,
	   flush_shs:1,
	   reserved:3;
	   tcp_flags_set:1,
	   reserved:2;
};

/* Descriptor management */
+17 −5
Original line number Diff line number Diff line
@@ -744,22 +744,23 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
	struct rmnet_priv *priv = netdev_priv(coal_skb->dev);
	__sum16 *check = NULL;
	u32 alloc_len;
	u32 dlen = coal_meta->data_len * coal_meta->pkt_count;
	u32 hlen = coal_meta->ip_len + coal_meta->trans_len;
	bool zero_csum = false;

	/* We can avoid copying the data if the SKB we got from the lower-level
	 * drivers was nonlinear.
	 */
	if (skb_is_nonlinear(coal_skb))
		alloc_len = coal_meta->ip_len + coal_meta->trans_len;
		alloc_len = hlen;
	else
		alloc_len = coal_meta->ip_len + coal_meta->trans_len +
			    (coal_meta->data_len * coal_meta->pkt_count);
		alloc_len = hlen + dlen;

	skbn = alloc_skb(alloc_len, GFP_ATOMIC);
	if (!skbn)
		return;

	skb_reserve(skbn, coal_meta->ip_len + coal_meta->trans_len);
	skb_reserve(skbn, hlen);
	rmnet_map_nonlinear_copy(coal_skb, coal_meta, skbn);

	/* Push transport header and update necessary fields */
@@ -771,6 +772,17 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,

		th->seq = htonl(ntohl(th->seq) + coal_meta->data_offset);
		check = &th->check;

		/* Don't allow dangerous flags to be set in any segment but the
		 * last one.
		 */
		if (th->fin || th->psh) {
			if (hlen + coal_meta->data_offset + dlen <
			    coal_skb->len) {
				th->fin = 0;
				th->psh = 0;
			}
		}
	} else if (coal_meta->trans_proto == IPPROTO_UDP) {
		struct udphdr *uh = udp_hdr(skbn);

@@ -846,7 +858,7 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb,
	__skb_queue_tail(list, skbn);

	/* Update meta information to move past the data we just segmented */
	coal_meta->data_offset += coal_meta->data_len * coal_meta->pkt_count;
	coal_meta->data_offset += dlen;
	coal_meta->pkt_id = pkt_id + 1;
	coal_meta->pkt_count = 0;
}