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: Sean Tranchetti <stranche@codeaurora.org>
This commit is contained in:
Sean Tranchetti 2020-10-29 10:59:47 -07:00
commit 253b60e402
3 changed files with 48 additions and 6 deletions

View file

@ -849,6 +849,16 @@ skip_frags:
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;

View file

@ -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 */

View file

@ -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;
}