github.com/cilium/cilium@v1.16.2/bpf/lib/ipv4.h (about)

     1  /* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
     2  /* Copyright Authors of Cilium */
     3  
     4  #pragma once
     5  
     6  #include <linux/ip.h>
     7  
     8  #include "dbg.h"
     9  #include "l4.h"
    10  #include "metrics.h"
    11  
    12  #define IPV4_SADDR_OFF		offsetof(struct iphdr, saddr)
    13  #define IPV4_DADDR_OFF		offsetof(struct iphdr, daddr)
    14  
    15  struct ipv4_frag_id {
    16  	__be32	daddr;
    17  	__be32	saddr;
    18  	__be16	id;		/* L4 datagram identifier */
    19  	__u8	proto;
    20  	__u8	pad;
    21  } __packed;
    22  
    23  struct ipv4_frag_l4ports {
    24  	__be16	sport;
    25  	__be16	dport;
    26  } __packed;
    27  
    28  #ifdef ENABLE_IPV4_FRAGMENTS
    29  struct {
    30  	__uint(type, BPF_MAP_TYPE_LRU_HASH);
    31  	__type(key, struct ipv4_frag_id);
    32  	__type(value, struct ipv4_frag_l4ports);
    33  	__uint(pinning, LIBBPF_PIN_BY_NAME);
    34  	__uint(max_entries, CILIUM_IPV4_FRAG_MAP_MAX_ENTRIES);
    35  } IPV4_FRAG_DATAGRAMS_MAP __section_maps_btf;
    36  #endif
    37  
    38  static __always_inline int
    39  ipv4_csum_update_by_value(struct __ctx_buff *ctx, int l3_off, __u64 old_val,
    40  			  __u64 new_val, __u32 len)
    41  {
    42  	return l3_csum_replace(ctx, l3_off + offsetof(struct iphdr, check),
    43  			       old_val, new_val, len);
    44  }
    45  
    46  static __always_inline int
    47  ipv4_csum_update_by_diff(struct __ctx_buff *ctx, int l3_off, __u64 diff)
    48  {
    49  	return l3_csum_replace(ctx, l3_off + offsetof(struct iphdr, check),
    50  			       0, diff, 0);
    51  }
    52  
    53  static __always_inline int ipv4_load_daddr(struct __ctx_buff *ctx, int off,
    54  					   __u32 *dst)
    55  {
    56  	return ctx_load_bytes(ctx, off + offsetof(struct iphdr, daddr), dst, 4);
    57  }
    58  
    59  static __always_inline int ipv4_dec_ttl(struct __ctx_buff *ctx, int off,
    60  					struct iphdr *ip4)
    61  {
    62  	__u8 new_ttl, ttl = ip4->ttl;
    63  
    64  	if (ttl <= 1)
    65  		return DROP_TTL_EXCEEDED;
    66  
    67  	new_ttl = ttl - 1;
    68  	ip4->ttl = new_ttl;
    69  
    70  	/* l3_csum_replace() takes at min 2 bytes, zero extended. */
    71  	if (ipv4_csum_update_by_value(ctx, off, ttl, new_ttl, 2) < 0)
    72  		return DROP_CSUM_L3;
    73  
    74  	return 0;
    75  }
    76  
    77  static __always_inline int ipv4_hdrlen(const struct iphdr *ip4)
    78  {
    79  	return ip4->ihl * 4;
    80  }
    81  
    82  static __always_inline bool ipv4_is_fragment(const struct iphdr *ip4)
    83  {
    84  	/* The frag_off portion of the header consists of:
    85  	 *
    86  	 * +----+----+----+----------------------------------+
    87  	 * | RS | DF | MF | ...13 bits of fragment offset... |
    88  	 * +----+----+----+----------------------------------+
    89  	 *
    90  	 * If "More fragments" or the offset is nonzero, then this is an IP
    91  	 * fragment (RFC791).
    92  	 */
    93  	return ip4->frag_off & bpf_htons(0x3FFF);
    94  }
    95  
    96  static __always_inline bool ipv4_is_not_first_fragment(const struct iphdr *ip4)
    97  {
    98  	/* Ignore "More fragments" bit to catch all fragments but the first */
    99  	return ip4->frag_off & bpf_htons(0x1FFF);
   100  }
   101  
   102  /* Simply a reverse of ipv4_is_not_first_fragment to avoid double negative. */
   103  static __always_inline bool ipv4_has_l4_header(const struct iphdr *ip4)
   104  {
   105  	return !ipv4_is_not_first_fragment(ip4);
   106  }
   107  
   108  static __always_inline bool ipv4_is_in_subnet(__be32 addr,
   109  					      __be32 subnet, int prefixlen)
   110  {
   111  	return (addr & bpf_htonl(~((1 << (32 - prefixlen)) - 1))) == subnet;
   112  }
   113  
   114  #ifdef ENABLE_IPV4_FRAGMENTS
   115  static __always_inline int
   116  ipv4_frag_get_l4ports(const struct ipv4_frag_id *frag_id,
   117  		      struct ipv4_frag_l4ports *ports)
   118  {
   119  	struct ipv4_frag_l4ports *tmp;
   120  
   121  	tmp = map_lookup_elem(&IPV4_FRAG_DATAGRAMS_MAP, frag_id);
   122  	if (!tmp)
   123  		return DROP_FRAG_NOT_FOUND;
   124  
   125  	/* Do not make ports a pointer to map data, copy from map */
   126  	memcpy(ports, tmp, sizeof(*ports));
   127  	return 0;
   128  }
   129  
   130  static __always_inline int
   131  ipv4_handle_fragmentation(struct __ctx_buff *ctx,
   132  			  const struct iphdr *ip4, int l4_off,
   133  			  enum ct_dir ct_dir,
   134  			  struct ipv4_frag_l4ports *ports,
   135  			  bool *has_l4_header)
   136  {
   137  	bool is_fragment, not_first_fragment;
   138  	int ret;
   139  
   140  	struct ipv4_frag_id frag_id = {
   141  		.daddr = ip4->daddr,
   142  		.saddr = ip4->saddr,
   143  		.id = ip4->id,
   144  		.proto = ip4->protocol,
   145  		.pad = 0,
   146  	};
   147  
   148  	is_fragment = ipv4_is_fragment(ip4);
   149  
   150  	if (unlikely(is_fragment)) {
   151  		not_first_fragment = ipv4_is_not_first_fragment(ip4);
   152  		if (has_l4_header)
   153  			*has_l4_header = !not_first_fragment;
   154  
   155  		if (likely(not_first_fragment))
   156  			return ipv4_frag_get_l4ports(&frag_id, ports);
   157  	}
   158  
   159  	/* load sport + dport into tuple */
   160  	ret = l4_load_ports(ctx, l4_off, (__be16 *)ports);
   161  	if (ret < 0)
   162  		return DROP_CT_INVALID_HDR;
   163  
   164  	if (unlikely(is_fragment)) {
   165  		/* First logical fragment for this datagram (not necessarily the first
   166  		 * we receive). Fragment has L4 header, create an entry in datagrams map.
   167  		 */
   168  		if (map_update_elem(&IPV4_FRAG_DATAGRAMS_MAP, &frag_id, ports, BPF_ANY))
   169  			update_metrics(ctx_full_len(ctx), ct_to_metrics_dir(ct_dir),
   170  				       REASON_FRAG_PACKET_UPDATE);
   171  
   172  		/* Do not return an error if map update failed, as nothing prevents us
   173  		 * to process the current packet normally.
   174  		 */
   175  	}
   176  
   177  	return 0;
   178  }
   179  #endif
   180  
   181  static __always_inline int
   182  ipv4_load_l4_ports(struct __ctx_buff *ctx, struct iphdr *ip4 __maybe_unused,
   183  		   int l4_off, enum ct_dir dir __maybe_unused,
   184  		   __be16 *ports, bool *has_l4_header __maybe_unused)
   185  {
   186  #ifdef ENABLE_IPV4_FRAGMENTS
   187  	return ipv4_handle_fragmentation(ctx, ip4, l4_off, dir,
   188  					 (struct ipv4_frag_l4ports *)ports,
   189  					 has_l4_header);
   190  #else
   191  	if (l4_load_ports(ctx, l4_off, ports) < 0)
   192  		return DROP_CT_INVALID_HDR;
   193  #endif
   194  
   195  	return CTX_ACT_OK;
   196  }