1 /** @file
2  * @brief Network packet filtering public header file
3  *
4  * The network packet filtering provides a mechanism for deciding the fate
5  * of an incoming or outgoing packet based on a set of basic rules.
6  */
7 
8 /*
9  * Copyright (c) 2021 BayLibre SAS
10  *
11  * SPDX-License-Identifier: Apache-2.0
12  */
13 
14 #ifndef ZEPHYR_INCLUDE_NET_PKT_FILTER_H_
15 #define ZEPHYR_INCLUDE_NET_PKT_FILTER_H_
16 
17 #include <limits.h>
18 #include <stdbool.h>
19 #include <zephyr/sys/slist.h>
20 #include <zephyr/net/net_core.h>
21 #include <zephyr/net/ethernet.h>
22 
23 #ifdef __cplusplus
24 extern "C" {
25 #endif
26 
27 /**
28  * @brief Network Packet Filter API
29  * @defgroup net_pkt_filter Network Packet Filter API
30  * @since 3.0
31  * @version 0.8.0
32  * @ingroup networking
33  * @{
34  */
35 
36 /** @cond INTERNAL_HIDDEN */
37 
38 struct npf_test;
39 
40 typedef bool (npf_test_fn_t)(struct npf_test *test, struct net_pkt *pkt);
41 
42 /** @endcond */
43 
44 /** @brief common filter test structure to be embedded into larger structures */
45 struct npf_test {
46 	npf_test_fn_t *fn;	/**< packet condition test function */
47 };
48 
49 /** @brief filter rule structure */
50 struct npf_rule {
51 	sys_snode_t node;               /**< Slist rule list node */
52 	enum net_verdict result;	/**< result if all tests pass */
53 	uint32_t nb_tests;		/**< number of tests for this rule */
54 	struct npf_test *tests[];	/**< pointers to @ref npf_test instances */
55 };
56 
57 /** @brief Default rule list termination for accepting a packet */
58 extern struct npf_rule npf_default_ok;
59 /** @brief Default rule list termination for rejecting a packet */
60 extern struct npf_rule npf_default_drop;
61 
62 /** @brief rule set for a given test location */
63 struct npf_rule_list {
64 	sys_slist_t rule_head;   /**< List head */
65 	struct k_spinlock lock;  /**< Lock protecting the list access */
66 };
67 
68 /** @brief  rule list applied to outgoing packets */
69 extern struct npf_rule_list npf_send_rules;
70 /** @brief rule list applied to incoming packets */
71 extern struct npf_rule_list npf_recv_rules;
72 /** @brief rule list applied for local incoming packets */
73 extern struct npf_rule_list npf_local_in_recv_rules;
74 /** @brief rule list applied for IPv4 incoming packets */
75 extern struct npf_rule_list npf_ipv4_recv_rules;
76 /** @brief rule list applied for IPv6 incoming packets */
77 extern struct npf_rule_list npf_ipv6_recv_rules;
78 
79 /**
80  * @brief Insert a rule at the front of given rule list
81  *
82  * @param rules the affected rule list
83  * @param rule the rule to be inserted
84  */
85 void npf_insert_rule(struct npf_rule_list *rules, struct npf_rule *rule);
86 
87 /**
88  * @brief Append a rule at the end of given rule list
89  *
90  * @param rules the affected rule list
91  * @param rule the rule to be appended
92  */
93 void npf_append_rule(struct npf_rule_list *rules, struct npf_rule *rule);
94 
95 /**
96  * @brief Remove a rule from the given rule list
97  *
98  * @param rules the affected rule list
99  * @param rule the rule to be removed
100  * @retval true if given rule was found in the rule list and removed
101  */
102 bool npf_remove_rule(struct npf_rule_list *rules, struct npf_rule *rule);
103 
104 /**
105  * @brief Remove all rules from the given rule list
106  *
107  * @param rules the affected rule list
108  * @retval true if at least one rule was removed from the rule list
109  */
110 bool npf_remove_all_rules(struct npf_rule_list *rules);
111 
112 /** @cond INTERNAL_HIDDEN */
113 
114 /* convenience shortcuts */
115 #define npf_insert_send_rule(rule) npf_insert_rule(&npf_send_rules, rule)
116 #define npf_insert_recv_rule(rule) npf_insert_rule(&npf_recv_rules, rule)
117 #define npf_append_send_rule(rule) npf_append_rule(&npf_send_rules, rule)
118 #define npf_append_recv_rule(rule) npf_append_rule(&npf_recv_rules, rule)
119 #define npf_remove_send_rule(rule) npf_remove_rule(&npf_send_rules, rule)
120 #define npf_remove_recv_rule(rule) npf_remove_rule(&npf_recv_rules, rule)
121 #define npf_remove_all_send_rules() npf_remove_all_rules(&npf_send_rules)
122 #define npf_remove_all_recv_rules() npf_remove_all_rules(&npf_recv_rules)
123 
124 #ifdef CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK
125 #define npf_insert_local_in_recv_rule(rule) npf_insert_rule(&npf_local_in_recv_rules, rule)
126 #define npf_append_local_in_recv_rule(rule) npf_append_rule(&npf_local_in_recv_rules, rule)
127 #define npf_remove_local_in_recv_rule(rule) npf_remove_rule(&npf_local_in_recv_rules, rule)
128 #define npf_remove_all_local_in_recv_rules() npf_remove_all_rules(&npf_local_in_recv_rules)
129 #endif /* CONFIG_NET_PKT_FILTER_LOCAL_IN_HOOK */
130 
131 #ifdef CONFIG_NET_PKT_FILTER_IPV4_HOOK
132 #define npf_insert_ipv4_recv_rule(rule) npf_insert_rule(&npf_ipv4_recv_rules, rule)
133 #define npf_append_ipv4_recv_rule(rule) npf_append_rule(&npf_ipv4_recv_rules, rule)
134 #define npf_remove_ipv4_recv_rule(rule) npf_remove_rule(&npf_ipv4_recv_rules, rule)
135 #define npf_remove_all_ipv4_recv_rules() npf_remove_all_rules(&npf_ipv4_recv_rules)
136 #endif /* CONFIG_NET_PKT_FILTER_IPV4_HOOK */
137 
138 #ifdef CONFIG_NET_PKT_FILTER_IPV6_HOOK
139 #define npf_insert_ipv6_recv_rule(rule) npf_insert_rule(&npf_ipv6_recv_rules, rule)
140 #define npf_append_ipv6_recv_rule(rule) npf_append_rule(&npf_ipv6_recv_rules, rule)
141 #define npf_remove_ipv6_recv_rule(rule) npf_remove_rule(&npf_ipv6_recv_rules, rule)
142 #define npf_remove_all_ipv6_recv_rules() npf_remove_all_rules(&npf_ipv6_recv_rules)
143 #endif /* CONFIG_NET_PKT_FILTER_IPV6_HOOK */
144 
145 /** @endcond */
146 
147 /**
148  * @brief Statically define one packet filter rule
149  *
150  * This creates a rule from a variable amount of filter conditions.
151  * This rule can then be inserted or appended to the rule list for a given
152  * network packet path.
153  *
154  * Example:
155  *
156  * @code{.c}
157  *
158  *     static NPF_SIZE_MAX(maxsize_200, 200);
159  *     static NPF_ETH_TYPE_MATCH(ip_packet, NET_ETH_PTYPE_IP);
160  *
161  *     static NPF_RULE(small_ip_pkt, NET_OK, ip_packet, maxsize_200);
162  *
163  *     void install_my_filter(void)
164  *     {
165  *         npf_insert_recv_rule(&npf_default_drop);
166  *         npf_insert_recv_rule(&small_ip_pkt);
167  *     }
168  *
169  * @endcode
170  *
171  * The above would accept IP packets that are 200 bytes or smaller, and drop
172  * all other packets.
173  *
174  * Another (less efficient) way to create the same result could be:
175  *
176  * @code{.c}
177  *
178  *     static NPF_SIZE_MIN(minsize_201, 201);
179  *     static NPF_ETH_TYPE_UNMATCH(not_ip_packet, NET_ETH_PTYPE_IP);
180  *
181  *     static NPF_RULE(reject_big_pkts, NET_DROP, minsize_201);
182  *     static NPF_RULE(reject_non_ip, NET_DROP, not_ip_packet);
183  *
184  *     void install_my_filter(void) {
185  *         npf_append_recv_rule(&reject_big_pkts);
186  *         npf_append_recv_rule(&reject_non_ip);
187  *         npf_append_recv_rule(&npf_default_ok);
188  *     }
189  *
190  * @endcode
191  *
192  * The first rule in the list for which all conditions are true determines
193  * the fate of the packet. If one condition is false then the next rule in
194  * the list is evaluated.
195  *
196  * @param _name Name for this rule.
197  * @param _result Fate of the packet if all conditions are true, either
198  *                <tt>NET_OK</tt> or <tt>NET_DROP</tt>.
199  * @param ... List of conditions for this rule.
200  */
201 #define NPF_RULE(_name, _result, ...) \
202 	struct npf_rule _name = { \
203 		.result = (_result), \
204 		.nb_tests = NUM_VA_ARGS_LESS_1(__VA_ARGS__) + 1, \
205 		.tests = { FOR_EACH(Z_NPF_TEST_ADDR, (,), __VA_ARGS__) }, \
206 	}
207 
208 #define Z_NPF_TEST_ADDR(arg) &arg.test
209 
210 /** @} */
211 
212 /**
213  * @defgroup npf_basic_cond Basic Filter Conditions
214  * @since 3.0
215  * @version 0.8.0
216  * @ingroup net_pkt_filter
217  * @{
218  */
219 
220 /** @cond INTERNAL_HIDDEN */
221 
222 struct npf_test_iface {
223 	struct npf_test test;
224 	struct net_if *iface;
225 };
226 
227 extern npf_test_fn_t npf_iface_match;
228 extern npf_test_fn_t npf_iface_unmatch;
229 extern npf_test_fn_t npf_orig_iface_match;
230 extern npf_test_fn_t npf_orig_iface_unmatch;
231 
232 /** @endcond */
233 
234 /**
235  * @brief Statically define an "interface match" packet filter condition
236  *
237  * @param _name Name of the condition
238  * @param _iface Interface to match
239  */
240 #define NPF_IFACE_MATCH(_name, _iface) \
241 	struct npf_test_iface _name = { \
242 		.iface = (_iface), \
243 		.test.fn = npf_iface_match, \
244 	}
245 
246 /**
247  * @brief Statically define an "interface unmatch" packet filter condition
248  *
249  * @param _name Name of the condition
250  * @param _iface Interface to exclude
251  */
252 #define NPF_IFACE_UNMATCH(_name, _iface) \
253 	struct npf_test_iface _name = { \
254 		.iface = (_iface), \
255 		.test.fn = npf_iface_unmatch, \
256 	}
257 
258 /**
259  * @brief Statically define an "orig interface match" packet filter condition
260  *
261  * @param _name Name of the condition
262  * @param _iface Interface to match
263  */
264 #define NPF_ORIG_IFACE_MATCH(_name, _iface) \
265 	struct npf_test_iface _name = { \
266 		.iface = (_iface), \
267 		.test.fn = npf_orig_iface_match, \
268 	}
269 
270 /**
271  * @brief Statically define an "orig interface unmatch" packet filter condition
272  *
273  * @param _name Name of the condition
274  * @param _iface Interface to exclude
275  */
276 #define NPF_ORIG_IFACE_UNMATCH(_name, _iface) \
277 	struct npf_test_iface _name = { \
278 		.iface = (_iface), \
279 		.test.fn = npf_orig_iface_unmatch, \
280 	}
281 
282 /** @cond INTERNAL_HIDDEN */
283 
284 struct npf_test_size_bounds {
285 	struct npf_test test;
286 	size_t min;
287 	size_t max;
288 };
289 
290 extern npf_test_fn_t npf_size_inbounds;
291 
292 /** @endcond */
293 
294 /**
295  * @brief Statically define a "data minimum size" packet filter condition
296  *
297  * @param _name Name of the condition
298  * @param _size Lower bound of the packet's data size
299  */
300 #define NPF_SIZE_MIN(_name, _size) \
301 	struct npf_test_size_bounds _name = { \
302 		.min = (_size), \
303 		.max = SIZE_MAX, \
304 		.test.fn = npf_size_inbounds, \
305 	}
306 
307 /**
308  * @brief Statically define a "data maximum size" packet filter condition
309  *
310  * @param _name Name of the condition
311  * @param _size Higher bound of the packet's data size
312  */
313 #define NPF_SIZE_MAX(_name, _size) \
314 	struct npf_test_size_bounds _name = { \
315 		.min = 0, \
316 		.max = (_size), \
317 		.test.fn = npf_size_inbounds, \
318 	}
319 
320 /**
321  * @brief Statically define a "data bounded size" packet filter condition
322  *
323  * @param _name Name of the condition
324  * @param _min_size Lower bound of the packet's data size
325  * @param _max_size Higher bound of the packet's data size
326  */
327 #define NPF_SIZE_BOUNDS(_name, _min_size, _max_size) \
328 	struct npf_test_size_bounds _name = { \
329 		.min = (_min_size), \
330 		.max = (_max_size), \
331 		.test.fn = npf_size_inbounds, \
332 	}
333 
334 /** @cond INTERNAL_HIDDEN */
335 
336 struct npf_test_ip {
337 	struct npf_test test;
338 	uint8_t addr_family;
339 	void *ipaddr;
340 	uint32_t ipaddr_num;
341 };
342 
343 extern npf_test_fn_t npf_ip_src_addr_match;
344 extern npf_test_fn_t npf_ip_src_addr_unmatch;
345 
346 /** @endcond */
347 
348 /**
349  * @brief Statically define a "ip address allowlist" packet filter condition
350  *
351  * This tests if the packet source ip address matches any of the ip
352  * addresses contained in the provided set.
353  *
354  * @param _name Name of the condition
355  * @param _ip_addr_array Array of <tt>struct in_addr</tt> or <tt>struct in6_addr</tt> items to test
356  *against
357  * @param _ip_addr_num number of IP addresses in the array
358  * @param _af Addresses family type (AF_INET / AF_INET6) in the array
359  */
360 #define NPF_IP_SRC_ADDR_ALLOWLIST(_name, _ip_addr_array, _ip_addr_num, _af) \
361 	struct npf_test_ip _name = { \
362 		.addr_family = _af, \
363 		.ipaddr = (_ip_addr_array), \
364 		.ipaddr_num = _ip_addr_num, \
365 		.test.fn = npf_ip_src_addr_match, \
366 	}
367 
368 /**
369  * @brief Statically define a "ip address blocklist" packet filter condition
370  *
371  * This tests if the packet source ip address matches any of the ip
372  * addresses contained in the provided set.
373  *
374  * @param _name Name of the condition
375  * @param _ip_addr_array Array of <tt>struct in_addr</tt> or <tt>struct in6_addr</tt> items to test
376  *against
377  * @param _ip_addr_num number of IP addresses in the array
378  * @param _af Addresses family type (AF_INET / AF_INET6) in the array
379  */
380 #define NPF_IP_SRC_ADDR_BLOCKLIST(_name, _ip_addr_array, _ip_addr_num, _af) \
381 	struct npf_test_ip _name = { \
382 		.addr_family = _af, \
383 		.ipaddr = (_ip_addr_array), \
384 		.ipaddr_num = _ip_addr_num, \
385 		.test.fn = npf_ip_src_addr_unmatch, \
386 	}
387 
388 /** @} */
389 
390 /**
391  * @defgroup npf_eth_cond Ethernet Filter Conditions
392  * @ingroup net_pkt_filter
393  * @since 3.0
394  * @version 0.8.0
395  * @{
396  */
397 
398 /** @cond INTERNAL_HIDDEN */
399 
400 struct npf_test_eth_addr {
401 	struct npf_test test;
402 	unsigned int nb_addresses;
403 	struct net_eth_addr *addresses;
404 	struct net_eth_addr mask;
405 };
406 
407 extern npf_test_fn_t npf_eth_src_addr_match;
408 extern npf_test_fn_t npf_eth_src_addr_unmatch;
409 extern npf_test_fn_t npf_eth_dst_addr_match;
410 extern npf_test_fn_t npf_eth_dst_addr_unmatch;
411 
412 /** @endcond */
413 
414 /**
415  * @brief Statically define a "source address match" packet filter condition
416  *
417  * This tests if the packet source address matches any of the Ethernet
418  * addresses contained in the provided set.
419  *
420  * @param _name Name of the condition
421  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
422  */
423 #define NPF_ETH_SRC_ADDR_MATCH(_name, _addr_array) \
424 	struct npf_test_eth_addr _name = { \
425 		.addresses = (_addr_array), \
426 		.nb_addresses = ARRAY_SIZE(_addr_array), \
427 		.test.fn = npf_eth_src_addr_match, \
428 		.mask.addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, \
429 	}
430 
431 /**
432  * @brief Statically define a "source address unmatch" packet filter condition
433  *
434  * This tests if the packet source address matches none of the Ethernet
435  * addresses contained in the provided set.
436  *
437  * @param _name Name of the condition
438  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
439  */
440 #define NPF_ETH_SRC_ADDR_UNMATCH(_name, _addr_array) \
441 	struct npf_test_eth_addr _name = { \
442 		.addresses = (_addr_array), \
443 		.nb_addresses = ARRAY_SIZE(_addr_array), \
444 		.test.fn = npf_eth_src_addr_unmatch, \
445 		.mask.addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, \
446 	}
447 
448 /**
449  * @brief Statically define a "destination address match" packet filter condition
450  *
451  * This tests if the packet destination address matches any of the Ethernet
452  * addresses contained in the provided set.
453  *
454  * @param _name Name of the condition
455  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
456  */
457 #define NPF_ETH_DST_ADDR_MATCH(_name, _addr_array) \
458 	struct npf_test_eth_addr _name = { \
459 		.addresses = (_addr_array), \
460 		.nb_addresses = ARRAY_SIZE(_addr_array), \
461 		.test.fn = npf_eth_dst_addr_match, \
462 		.mask.addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, \
463 	}
464 
465 /**
466  * @brief Statically define a "destination address unmatch" packet filter condition
467  *
468  * This tests if the packet destination address matches none of the Ethernet
469  * addresses contained in the provided set.
470  *
471  * @param _name Name of the condition
472  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
473  */
474 #define NPF_ETH_DST_ADDR_UNMATCH(_name, _addr_array) \
475 	struct npf_test_eth_addr _name = { \
476 		.addresses = (_addr_array), \
477 		.nb_addresses = ARRAY_SIZE(_addr_array), \
478 		.test.fn = npf_eth_dst_addr_unmatch, \
479 		.mask.addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, \
480 	}
481 
482 /**
483  * @brief Statically define a "source address match with mask" packet filter condition
484  *
485  * This tests if the packet source address matches any of the Ethernet
486  * addresses contained in the provided set after applying specified mask.
487  *
488  * @param _name Name of the condition
489  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
490  * @param ... up to 6 mask bytes
491  */
492 #define NPF_ETH_SRC_ADDR_MASK_MATCH(_name, _addr_array, ...) \
493 	struct npf_test_eth_addr _name = { \
494 		.addresses = (_addr_array), \
495 		.nb_addresses = ARRAY_SIZE(_addr_array), \
496 		.mask.addr = { __VA_ARGS__ }, \
497 		.test.fn = npf_eth_src_addr_match, \
498 	}
499 
500 /**
501  * @brief Statically define a "destination address match with mask" packet filter condition
502  *
503  * This tests if the packet destination address matches any of the Ethernet
504  * addresses contained in the provided set after applying specified mask.
505  *
506  * @param _name Name of the condition
507  * @param _addr_array Array of <tt>struct net_eth_addr</tt> items to test against
508  * @param ... up to 6 mask bytes
509  */
510 #define NPF_ETH_DST_ADDR_MASK_MATCH(_name, _addr_array, ...) \
511 	struct npf_test_eth_addr _name = { \
512 		.addresses = (_addr_array), \
513 		.nb_addresses = ARRAY_SIZE(_addr_array), \
514 		.mask.addr = { __VA_ARGS__ }, \
515 		.test.fn = npf_eth_dst_addr_match, \
516 	}
517 
518 /** @cond INTERNAL_HIDDEN */
519 
520 struct npf_test_eth_type {
521 	struct npf_test test;
522 	uint16_t type;			/* type in network order */
523 };
524 
525 extern npf_test_fn_t npf_eth_type_match;
526 extern npf_test_fn_t npf_eth_type_unmatch;
527 
528 /** @endcond */
529 
530 /**
531  * @brief Statically define an "Ethernet type match" packet filter condition
532  *
533  * @param _name Name of the condition
534  * @param _type Ethernet type to match
535  */
536 #define NPF_ETH_TYPE_MATCH(_name, _type) \
537 	struct npf_test_eth_type _name = { \
538 		.type = htons(_type), \
539 		.test.fn = npf_eth_type_match, \
540 	}
541 
542 /**
543  * @brief Statically define an "Ethernet type unmatch" packet filter condition
544  *
545  * @param _name Name of the condition
546  * @param _type Ethernet type to exclude
547  */
548 #define NPF_ETH_TYPE_UNMATCH(_name, _type) \
549 	struct npf_test_eth_type _name = { \
550 		.type = htons(_type), \
551 		.test.fn = npf_eth_type_unmatch, \
552 	}
553 
554 /** @} */
555 
556 #ifdef __cplusplus
557 }
558 #endif
559 
560 #endif /* ZEPHYR_INCLUDE_NET_PKT_FILTER_H_ */
561