qosify: add support for keeping stats
[project/qosify.git] / bpf_skb_utils.h
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
4 * Version: 2022-09-21
5 */
6 #ifndef __BPF_SKB_UTILS_H
7 #define __BPF_SKB_UTILS_H
8
9 #include <uapi/linux/bpf.h>
10 #include <uapi/linux/if_ether.h>
11 #include <uapi/linux/ip.h>
12 #include <uapi/linux/ipv6.h>
13 #include <linux/ip.h>
14 #include <net/ipv6.h>
15 #include <bpf/bpf_helpers.h>
16 #include <bpf/bpf_endian.h>
17
18 struct skb_parser_info {
19 struct __sk_buff *skb;
20 __u32 offset;
21 int proto;
22 };
23
24 static __always_inline void *__skb_data(struct __sk_buff *skb)
25 {
26 return (void *)(long)READ_ONCE(skb->data);
27 }
28
29 static __always_inline void *
30 skb_ptr(struct __sk_buff *skb, __u32 offset, __u32 len)
31 {
32 void *ptr = __skb_data(skb) + offset;
33 void *end = (void *)(long)(skb->data_end);
34
35 if (ptr + len >= end)
36 return NULL;
37
38 return ptr;
39 }
40
41 static __always_inline void *
42 skb_info_ptr(struct skb_parser_info *info, __u32 len)
43 {
44 __u32 offset = info->offset;
45 return skb_ptr(info->skb, offset, len);
46 }
47
48 static __always_inline void
49 skb_parse_init(struct skb_parser_info *info, struct __sk_buff *skb)
50 {
51 *info = (struct skb_parser_info){
52 .skb = skb
53 };
54 }
55
56 static __always_inline struct ethhdr *
57 skb_parse_ethernet(struct skb_parser_info *info)
58 {
59 struct ethhdr *eth;
60 int len;
61
62 len = sizeof(*eth) + 2 * sizeof(struct vlan_hdr) + sizeof(struct ipv6hdr);
63 if (len > info->skb->len)
64 len = info->skb->len;
65 bpf_skb_pull_data(info->skb, len);
66
67 eth = skb_info_ptr(info, sizeof(*eth));
68 if (!eth)
69 return NULL;
70
71 info->proto = eth->h_proto;
72 info->offset += sizeof(*eth);
73
74 return eth;
75 }
76
77 static __always_inline struct vlan_hdr *
78 skb_parse_vlan(struct skb_parser_info *info)
79 {
80 struct vlan_hdr *vlh;
81
82 if (info->proto != bpf_htons(ETH_P_8021Q) &&
83 info->proto != bpf_htons(ETH_P_8021AD))
84 return NULL;
85
86 vlh = skb_info_ptr(info, sizeof(*vlh));
87 if (!vlh)
88 return NULL;
89
90 info->proto = vlh->h_vlan_encapsulated_proto;
91 info->offset += sizeof(*vlh);
92
93 return vlh;
94 }
95
96 static __always_inline struct iphdr *
97 skb_parse_ipv4(struct skb_parser_info *info, int min_l4_bytes)
98 {
99 struct iphdr *iph;
100 int proto, hdr_len;
101 __u32 pull_len;
102
103 if (info->proto != bpf_htons(ETH_P_IP))
104 return NULL;
105
106 iph = skb_info_ptr(info, sizeof(*iph));
107 if (!iph)
108 return NULL;
109
110 hdr_len = iph->ihl * 4;
111 hdr_len = READ_ONCE(hdr_len) & 0xff;
112 if (hdr_len < sizeof(*iph))
113 return NULL;
114
115 pull_len = info->offset + hdr_len + min_l4_bytes;
116 if (pull_len > info->skb->len)
117 pull_len = info->skb->len;
118
119 if (bpf_skb_pull_data(info->skb, pull_len))
120 return NULL;
121
122 iph = skb_info_ptr(info, sizeof(*iph));
123 if (!iph)
124 return NULL;
125
126 info->proto = iph->protocol;
127 info->offset += hdr_len;
128
129 return iph;
130 }
131
132 static __always_inline struct ipv6hdr *
133 skb_parse_ipv6(struct skb_parser_info *info, int max_l4_bytes)
134 {
135 struct ipv6hdr *ip6h;
136 __u32 pull_len;
137
138 if (info->proto != bpf_htons(ETH_P_IPV6))
139 return NULL;
140
141 pull_len = info->offset + sizeof(*ip6h) + max_l4_bytes;
142 if (pull_len > info->skb->len)
143 pull_len = info->skb->len;
144
145 if (bpf_skb_pull_data(info->skb, pull_len))
146 return NULL;
147
148 ip6h = skb_info_ptr(info, sizeof(*ip6h));
149 if (!ip6h)
150 return NULL;
151
152 info->proto = READ_ONCE(ip6h->nexthdr);
153 info->offset += sizeof(*ip6h);
154
155 return ip6h;
156 }
157
158 static __always_inline struct tcphdr *
159 skb_parse_tcp(struct skb_parser_info *info)
160 {
161 struct tcphdr *tcph;
162
163 if (info->proto != IPPROTO_TCP)
164 return NULL;
165
166 tcph = skb_info_ptr(info, sizeof(*tcph));
167 if (!tcph)
168 return NULL;
169
170 info->offset += tcph->doff * 4;
171
172 return tcph;
173 }
174
175 #endif