Skip to content

Commit 9b7fd81

Browse files
tohojogregkh
authored andcommitted
sched: consistently handle layer3 header accesses in the presence of VLANs
[ Upstream commit d7bf2eb ] There are a couple of places in net/sched/ that check skb->protocol and act on the value there. However, in the presence of VLAN tags, the value stored in skb->protocol can be inconsistent based on whether VLAN acceleration is enabled. The commit quoted in the Fixes tag below fixed the users of skb->protocol to use a helper that will always see the VLAN ethertype. However, most of the callers don't actually handle the VLAN ethertype, but expect to find the IP header type in the protocol field. This means that things like changing the ECN field, or parsing diffserv values, stops working if there's a VLAN tag, or if there are multiple nested VLAN tags (QinQ). To fix this, change the helper to take an argument that indicates whether the caller wants to skip the VLAN tags or not. When skipping VLAN tags, we make sure to skip all of them, so behaviour is consistent even in QinQ mode. To make the helper usable from the ECN code, move it to if_vlan.h instead of pkt_sched.h. v3: - Remove empty lines - Move vlan variable definitions inside loop in skb_protocol() - Also use skb_protocol() helper in IP{,6}_ECN_decapsulate() and bpf_skb_ecn_set_ce() v2: - Use eth_type_vlan() helper in skb_protocol() - Also fix code that reads skb->protocol directly - Change a couple of 'if/else if' statements to switch constructs to avoid calling the helper twice Reported-by: Ilya Ponetayev <[email protected]> Fixes: d8b9605 ("net: sched: fix skb->protocol use in case of accelerated vlan path") Signed-off-by: Toke Høiland-Jørgensen <[email protected]> Signed-off-by: David S. Miller <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent aafe9dd commit 9b7fd81

File tree

19 files changed

+86
-51
lines changed

19 files changed

+86
-51
lines changed

include/linux/if_vlan.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,34 @@ static inline bool eth_type_vlan(__be16 ethertype)
308308
}
309309
}
310310

311+
/* A getter for the SKB protocol field which will handle VLAN tags consistently
312+
* whether VLAN acceleration is enabled or not.
313+
*/
314+
static inline __be16 skb_protocol(const struct sk_buff *skb, bool skip_vlan)
315+
{
316+
unsigned int offset = skb_mac_offset(skb) + sizeof(struct ethhdr);
317+
__be16 proto = skb->protocol;
318+
319+
if (!skip_vlan)
320+
/* VLAN acceleration strips the VLAN header from the skb and
321+
* moves it to skb->vlan_proto
322+
*/
323+
return skb_vlan_tag_present(skb) ? skb->vlan_proto : proto;
324+
325+
while (eth_type_vlan(proto)) {
326+
struct vlan_hdr vhdr, *vh;
327+
328+
vh = skb_header_pointer(skb, offset, sizeof(vhdr), &vhdr);
329+
if (!vh)
330+
break;
331+
332+
proto = vh->h_vlan_encapsulated_proto;
333+
offset += sizeof(vhdr);
334+
}
335+
336+
return proto;
337+
}
338+
311339
static inline bool vlan_hw_offload_capable(netdev_features_t features,
312340
__be16 proto)
313341
{

include/net/inet_ecn.h

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <linux/ip.h>
66
#include <linux/skbuff.h>
7+
#include <linux/if_vlan.h>
78

89
#include <net/inet_sock.h>
910
#include <net/dsfield.h>
@@ -172,7 +173,7 @@ static inline void ipv6_copy_dscp(unsigned int dscp, struct ipv6hdr *inner)
172173

173174
static inline int INET_ECN_set_ce(struct sk_buff *skb)
174175
{
175-
switch (skb->protocol) {
176+
switch (skb_protocol(skb, true)) {
176177
case cpu_to_be16(ETH_P_IP):
177178
if (skb_network_header(skb) + sizeof(struct iphdr) <=
178179
skb_tail_pointer(skb))
@@ -191,7 +192,7 @@ static inline int INET_ECN_set_ce(struct sk_buff *skb)
191192

192193
static inline int INET_ECN_set_ect1(struct sk_buff *skb)
193194
{
194-
switch (skb->protocol) {
195+
switch (skb_protocol(skb, true)) {
195196
case cpu_to_be16(ETH_P_IP):
196197
if (skb_network_header(skb) + sizeof(struct iphdr) <=
197198
skb_tail_pointer(skb))
@@ -272,12 +273,16 @@ static inline int IP_ECN_decapsulate(const struct iphdr *oiph,
272273
{
273274
__u8 inner;
274275

275-
if (skb->protocol == htons(ETH_P_IP))
276+
switch (skb_protocol(skb, true)) {
277+
case htons(ETH_P_IP):
276278
inner = ip_hdr(skb)->tos;
277-
else if (skb->protocol == htons(ETH_P_IPV6))
279+
break;
280+
case htons(ETH_P_IPV6):
278281
inner = ipv6_get_dsfield(ipv6_hdr(skb));
279-
else
282+
break;
283+
default:
280284
return 0;
285+
}
281286

282287
return INET_ECN_decapsulate(skb, oiph->tos, inner);
283288
}
@@ -287,12 +292,16 @@ static inline int IP6_ECN_decapsulate(const struct ipv6hdr *oipv6h,
287292
{
288293
__u8 inner;
289294

290-
if (skb->protocol == htons(ETH_P_IP))
295+
switch (skb_protocol(skb, true)) {
296+
case htons(ETH_P_IP):
291297
inner = ip_hdr(skb)->tos;
292-
else if (skb->protocol == htons(ETH_P_IPV6))
298+
break;
299+
case htons(ETH_P_IPV6):
293300
inner = ipv6_get_dsfield(ipv6_hdr(skb));
294-
else
301+
break;
302+
default:
295303
return 0;
304+
}
296305

297306
return INET_ECN_decapsulate(skb, ipv6_get_dsfield(oipv6h), inner);
298307
}

include/net/pkt_sched.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,6 @@ static inline void qdisc_run(struct Qdisc *q)
128128
}
129129
}
130130

131-
static inline __be16 tc_skb_protocol(const struct sk_buff *skb)
132-
{
133-
/* We need to take extra care in case the skb came via
134-
* vlan accelerated path. In that case, use skb->vlan_proto
135-
* as the original vlan header was already stripped.
136-
*/
137-
if (skb_vlan_tag_present(skb))
138-
return skb->vlan_proto;
139-
return skb->protocol;
140-
}
141-
142131
/* Calculate maximal size of packet seen by hard_start_xmit
143132
routine of this device.
144133
*/

net/core/filter.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5730,12 +5730,16 @@ BPF_CALL_1(bpf_skb_ecn_set_ce, struct sk_buff *, skb)
57305730
{
57315731
unsigned int iphdr_len;
57325732

5733-
if (skb->protocol == cpu_to_be16(ETH_P_IP))
5733+
switch (skb_protocol(skb, true)) {
5734+
case cpu_to_be16(ETH_P_IP):
57345735
iphdr_len = sizeof(struct iphdr);
5735-
else if (skb->protocol == cpu_to_be16(ETH_P_IPV6))
5736+
break;
5737+
case cpu_to_be16(ETH_P_IPV6):
57365738
iphdr_len = sizeof(struct ipv6hdr);
5737-
else
5739+
break;
5740+
default:
57385741
return 0;
5742+
}
57395743

57405744
if (skb_headlen(skb) < iphdr_len)
57415745
return 0;

net/sched/act_connmark.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,20 @@ static int tcf_connmark_act(struct sk_buff *skb, const struct tc_action *a,
4343
tcf_lastuse_update(&ca->tcf_tm);
4444
bstats_update(&ca->tcf_bstats, skb);
4545

46-
if (skb->protocol == htons(ETH_P_IP)) {
46+
switch (skb_protocol(skb, true)) {
47+
case htons(ETH_P_IP):
4748
if (skb->len < sizeof(struct iphdr))
4849
goto out;
4950

5051
proto = NFPROTO_IPV4;
51-
} else if (skb->protocol == htons(ETH_P_IPV6)) {
52+
break;
53+
case htons(ETH_P_IPV6):
5254
if (skb->len < sizeof(struct ipv6hdr))
5355
goto out;
5456

5557
proto = NFPROTO_IPV6;
56-
} else {
58+
break;
59+
default:
5760
goto out;
5861
}
5962

net/sched/act_csum.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ static int tcf_csum_act(struct sk_buff *skb, const struct tc_action *a,
587587
goto drop;
588588

589589
update_flags = params->update_flags;
590-
protocol = tc_skb_protocol(skb);
590+
protocol = skb_protocol(skb, false);
591591
again:
592592
switch (protocol) {
593593
case cpu_to_be16(ETH_P_IP):

net/sched/act_ct.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static u8 tcf_ct_skb_nf_family(struct sk_buff *skb)
100100
{
101101
u8 family = NFPROTO_UNSPEC;
102102

103-
switch (skb->protocol) {
103+
switch (skb_protocol(skb, true)) {
104104
case htons(ETH_P_IP):
105105
family = NFPROTO_IPV4;
106106
break;
@@ -222,6 +222,7 @@ static int ct_nat_execute(struct sk_buff *skb, struct nf_conn *ct,
222222
const struct nf_nat_range2 *range,
223223
enum nf_nat_manip_type maniptype)
224224
{
225+
__be16 proto = skb_protocol(skb, true);
225226
int hooknum, err = NF_ACCEPT;
226227

227228
/* See HOOK2MANIP(). */
@@ -233,14 +234,13 @@ static int ct_nat_execute(struct sk_buff *skb, struct nf_conn *ct,
233234
switch (ctinfo) {
234235
case IP_CT_RELATED:
235236
case IP_CT_RELATED_REPLY:
236-
if (skb->protocol == htons(ETH_P_IP) &&
237+
if (proto == htons(ETH_P_IP) &&
237238
ip_hdr(skb)->protocol == IPPROTO_ICMP) {
238239
if (!nf_nat_icmp_reply_translation(skb, ct, ctinfo,
239240
hooknum))
240241
err = NF_DROP;
241242
goto out;
242-
} else if (IS_ENABLED(CONFIG_IPV6) &&
243-
skb->protocol == htons(ETH_P_IPV6)) {
243+
} else if (IS_ENABLED(CONFIG_IPV6) && proto == htons(ETH_P_IPV6)) {
244244
__be16 frag_off;
245245
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
246246
int hdrlen = ipv6_skip_exthdr(skb,
@@ -993,4 +993,3 @@ MODULE_AUTHOR("Yossi Kuperman <[email protected]>");
993993
MODULE_AUTHOR("Marcelo Ricardo Leitner <[email protected]>");
994994
MODULE_DESCRIPTION("Connection tracking action");
995995
MODULE_LICENSE("GPL v2");
996-

net/sched/act_ctinfo.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,22 @@ static int tcf_ctinfo_act(struct sk_buff *skb, const struct tc_action *a,
9696
action = READ_ONCE(ca->tcf_action);
9797

9898
wlen = skb_network_offset(skb);
99-
if (tc_skb_protocol(skb) == htons(ETH_P_IP)) {
99+
switch (skb_protocol(skb, true)) {
100+
case htons(ETH_P_IP):
100101
wlen += sizeof(struct iphdr);
101102
if (!pskb_may_pull(skb, wlen))
102103
goto out;
103104

104105
proto = NFPROTO_IPV4;
105-
} else if (tc_skb_protocol(skb) == htons(ETH_P_IPV6)) {
106+
break;
107+
case htons(ETH_P_IPV6):
106108
wlen += sizeof(struct ipv6hdr);
107109
if (!pskb_may_pull(skb, wlen))
108110
goto out;
109111

110112
proto = NFPROTO_IPV6;
111-
} else {
113+
break;
114+
default:
112115
goto out;
113116
}
114117

net/sched/act_mpls.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static int tcf_mpls_act(struct sk_buff *skb, const struct tc_action *a,
8282
goto drop;
8383
break;
8484
case TCA_MPLS_ACT_PUSH:
85-
new_lse = tcf_mpls_get_lse(NULL, p, !eth_p_mpls(skb->protocol));
85+
new_lse = tcf_mpls_get_lse(NULL, p, !eth_p_mpls(skb_protocol(skb, true)));
8686
if (skb_mpls_push(skb, new_lse, p->tcfm_proto, mac_len,
8787
skb->dev && skb->dev->type == ARPHRD_ETHER))
8888
goto drop;

net/sched/act_skbedit.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static int tcf_skbedit_act(struct sk_buff *skb, const struct tc_action *a,
4141
if (params->flags & SKBEDIT_F_INHERITDSFIELD) {
4242
int wlen = skb_network_offset(skb);
4343

44-
switch (tc_skb_protocol(skb)) {
44+
switch (skb_protocol(skb, true)) {
4545
case htons(ETH_P_IP):
4646
wlen += sizeof(struct iphdr);
4747
if (!pskb_may_pull(skb, wlen))

0 commit comments

Comments
 (0)